2
votes

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?

1

1 Answers

3
votes

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