Rust Error conversion for generic FromStr, unwrap errors out with unsatisfied trait bounds

Viewed 1281

I am using the thiserror crate to create a custom error type

use thiserror::Error;

/// ParseTreeError enumerates all possible errors returned by this library.
#[derive(Error, Debug)]
pub enum ParseTreeError {
    /// Represents an empty source. For example, an empty text file being given
    /// as input to `count_words()`.
    #[error("Source contains no data")]
    EmptySource,

    /// Represents a failure to read from input.
    #[error("Read error")]
    ReadError,
}
pub struct Parser <'a, T>
where
    T: FromStr + PartialEq
{
    token_stream: &'a str,
    marker: (usize, usize),
}

impl<'a, T> Parser<'a, T>
where
    T: FromStr + PartialEq
{
    fn test_parse() -> Result<(), ParseTreeError> {
        let mut val = T::from_str("5").unwrap();
        Ok(())
    }
}

This throws the following error:

error[E0599]: the method `unwrap` exists for enum `std::result::Result<T, <T as FromStr>::Err>`, but its trait bounds were not satisfied
  --> src/tree/parser.rs:65:40
   |
65 |         let mut val = T::from_str("5").unwrap();
   |                                        ^^^^^^ method cannot be called on `std::result::Result<T, <T as FromStr>::Err>` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `<T as FromStr>::Err: Debug`

If I map the error like this

let mut val2 = T::from_str("5").map_err(|_| ParseTreeError::ReadError).unwrap();

Then no error is thrown.

If i try to use the ? operator i get the error

error[E0277]: `?` couldn't convert the error to `ParseTreeError`
  --> src/tree/parser.rs:65:40
   |
65 |         let mut val1 = T::from_str("5")?;
   |                                        ^ the trait `From<<T as FromStr>::Err>` is not implemented for `ParseTreeError`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = note: required by `from`

What do I need to do so that the ? operator can automatically convert the error to the correct error type without having to use map_err ?

1 Answers

Your issue is completely unrelated to thiserror, but can be reproduced via

use std::str::FromStr;

fn test_parse<T>() -> Result<(), ()>
where
    T: FromStr,
{
    let mut val = T::from_str("5").unwrap();
    Ok(())
}

So, what's the issue? The issue is hidden away in Result's unwrap. unwrap is only available if we can somehow show the E in Result<T, E>:

//      vvvvvvvvvvvvv
impl<T, E: fmt::Debug> Result<T, E> {
    //    ...
    
    fn unwrap(self) -> T {
        ...
    }
}

Where does E come from? Well, every FromStr implementation has to define the associated error type, Err. However, there are no constrains on Err. It might implement Debug, but it also might not. Since our trait bounds on test_parse say that we're fine to run over all Ts that implement just FromStr, we're too generic. We need to say that test_parse will only work for Ts that are FromStr and whose associated Err type can be shown via Debug:

use std::fmt;
use std::str::FromStr;

fn test_parse<T>() -> Result<(), ()>
where
    T: FromStr,
    <T as FromStr>::Err: fmt::Debug,             // <<<<
{
    let mut val = T::from_str("5").unwrap();
    Ok(())
}

Now test_parse cannot be used with all possible FromStr implementations anymore, but only those which also implement Debug on their associated Err type, which is exactly what we needed.

For the ? operator, we need to implement From<<T as FromStr>::Err> for ParseTreeError, however, that's blocked by E0207.

Related