Implement the `From` trait from an associated type

132 views Asked by At

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?

1

There are 1 answers

0
drewtato On BEST ANSWER

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)))
    }
}