简体   繁体   中英

Rust: Implement trait "From" for associated type (Error)

This is a follow-up question on my previous question: Rust: Read and map lines from stdin and handling different error types

I have created the following struct and function to read lines from stdin and parse them into integers and it works:

use std::io::BufRead;
use std::{io, num, str};

#[derive(Debug)]
enum InputError {
    IOError(io::Error),
    ParseIntError(num::ParseIntError),
}

impl From<io::Error> for InputError {
    fn from(e: io::Error) -> InputError {
        return InputError::IOError(e);
    }
}

impl From<num::ParseIntError> for InputError {
    fn from(e: num::ParseIntError) -> InputError {
        return InputError::ParseIntError(e);
    }
}

pub fn get_integer_lines<T>() -> Result<Vec<T>, InputError>
where
    T: str::FromStr,
{
    let stdin = io::stdin();
    let my_values: Result<Vec<_>, InputError> = stdin
        .lock()
        .lines()
        .map(|line| -> Result<T, InputError> { Ok(line?.parse::<T>()?) })
        .collect();
    my_values
}

Now, I thought that I would replace u32 for a type parameter T to allow for any kind of numeric type. To do this I assume that I need to restrict T to types implementing the FromStr trait and then somehow implement the From trait to allow conversion from FromStr::Err into my "InputError".

Following the error I first got

error[E0277]: `?` couldn't convert the error to `InputError`
  --> src/lib.rs:30:69
   |
30 |         .map(|line| -> Result<T, InputError> { Ok(line?.parse::<T>()?) })
   |                                                                     ^ the trait `std::convert::From<<T as std::str::FromStr>::Err>` is not implemented for `InputError`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = help: consider adding a `where InputError: std::convert::From<<T as std::str::FromStr>::Err>` bound
   = note: required by `std::convert::From::from`

I tried something like this:

impl std::convert::From<<T as std::str::FromStr>::Err> for InputError {
    fn from(e: <T as std::str::FromStr>::Err) -> InputError {
        return InputError::ParseIntError(e)
    }
} 

But that instead results in:

error[E0412]: cannot find type `T` in this scope
  --> src/lib.rs:22:26
   |
22 | impl std::convert::From<<T as std::str::FromStr>::Err> for InputError {
   |                          ^ not found in this scope

So basically what I want to express is something along the lines of: "I want to implement the trait From<T::Err> for my InputError for every T which also implements FromStr . Is this even possible and if so, how?

So basically what I want to express is something along the lines of: "I want to implement the trait From<T::Err> for my InputError for every T which also implements FromStr . Is this even possible and if so, how?

This isn't what the error is saying.

The trait FromStr has an associated type, Err . The error is saying that this associated error type cannot be converted to InputError .

First, let's simplify by getting rid of the type parameters:

fn get_integer_lines() -> Result<Vec<u32>, InputError> {
    let stdin = io::stdin();
    let my_values = stdin
        .lock()
        .lines()
        .map(|line| Ok(line?.parse()?))
        .collect();
    my_values
}

This works!

The associated Err type for u32 's FromStr implementation is ParseIntError and you have correctly implemented From<ParseIntError> for InputError .

The reason this doesn't work for T is because T 's FromStr::Err type could be anything. By using a type parameter, you are telling the compiler that you expect this function to work for any possible type T , but it only works for types where FromStr::Err can be converted to your InputError type.

The error message gives you a hint:

= help: consider adding a `where InputError: std::convert::From<<T as std::str::FromStr>::Err>` bound

So let's do that:

fn get_integer_lines<T>() -> Result<Vec<T>, InputError>
where
    T: str::FromStr,
    InputError: From<<T as str::FromStr>::Err>,
{
    let stdin = io::stdin();
    let my_values = stdin
        .lock()
        .lines()
        .map(|line| Ok(line?.parse()?))
        .collect();
    my_values
}

This tells the compiler that you expect the function to work for all possible T provided that :

  • T implements FromStr and,
  • the associated Err type from T 's FromStr implementation can be converted into an InputError

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM