Search code examples
rusttraitsassociated-types

Implement the `From` trait from an associated type


I'm writing an Error enum that represents failure to parse a value obtained from a program's arguments. Initially, it looks like this:

pub enum LoadingError {
    NoArg,
    CantParse
}

I'd like it to be easily converted from a parsing error. The str::parse function is defined as follows:

pub fn parse<F: FromStr>(&self) -> Result<F, F::Err> {
    FromStr::from_str(self)
}

It seems to me that, if I want to implement the From trait for my enum converting from any FromStr::Err, I would need my function to be generic on the FromStr trait. I tried doing something like this:

pub enum LoadingError<F: FromStr> {
    NoArg,
    CantParse(F::Err)
}

impl<F: FromStr> From<F::Err> for LoadingError<F> {
    fn from(err: F::Err) -> LoadingError<F> {
        LoadingError::CantParse(err)
    }
}

This, however, does not compile, with this error being returned:

error[E0119]: conflicting implementations of trait `From<LoadingError<_>>` for type `LoadingError<_>`
  --> src/main.rs:15:1
   |
15 | impl<F: FromStr> From<F::Err> for LoadingError<F> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `core`:
           - impl<T> From<T> for T;

I don't really see how implementations with these two different type signatures would be conflicting, and I couldn't find the definition for impl<T> From<T> for T in Rust's core code.

I've also tried using the thiserror crate, leading me to the same error.

#[derive(Error, Debug)]
pub enum LoadingError<F: FromStr> {
    #[error("can't find argument")]
    NoArg,
    #[error("couldn't parse")]
    CantParse(#[from] F::Err)
}

My question is: How can I implement the From trait for my type from any of the many Errs defined by the FromStr trait?


Solution

  • First, impl<T> From<T> for T is here.

    Second, any type can be FromStr::Err, including LoadingError. If it was possible to make an impl that only worked on types that were used in FromStr::Err, then it would be possible to change the meaning of your code from outside your crate by writing a FromStr impl that uses an error type that you assumed wasn't used in FromStr::Err.

    Instead, you should create From impls on the actual error types you're looking for, like ParseIntError.

    pub enum LoadingError<F> {
        NoArg,
        CantParse(F)
    }
    
    use std::num::ParseIntError;
    impl From<ParseIntError> for LoadingError<ParseIntError> {
        fn from(value: ParseIntError) -> Self {
            LoadingError::CantParse(value)
        }
    }
    

    Or you can create a method to convert any error into LoadingError and only use it on the result of parse.

    impl<F> LoadingError<F> {
        pub fn convert_parse<T>(result: Result<T, F>) -> Result<T, Self> {
            result.map_err(|e| LoadingError::CantParse(e))
        }
    }
    

    You can use this like:

    LoadingError::convert_parse("1".parse::<i32>())
    

    Using a generic on your error type can be problematic, so you may want to store a trait object instead (or use a trait object as the generic).

    use std::error::Error;
    
    pub enum LoadingError {
        NoArg,
        CantParse(Box<dyn Error>)
    }
    
    use std::num::ParseIntError;
    impl From<ParseIntError> for LoadingError {
        fn from(value: ParseIntError) -> Self {
            LoadingError::CantParse(Box::new(value))
        }
    }
    
    impl LoadingError {
        pub fn convert_parse<T, F: Error + 'static>(result: Result<T, F>) -> Result<T, Self> {
            result.map_err(|e| LoadingError::CantParse(Box::new(e)))
        }
    }