Closure that returns Result of generic type doesn't compile

Hello, I am attempting to write a general-purpose function for accepting user input and validating it like so:

fn prompt_input<T, F>(prompt: &str, error: &str, mut validator: F) -> T
where
    F: FnMut(&str) -> Result<T, Box<dyn Error>>,
{
    let mut input_str = String::new();
    loop {
        println!("{prompt}");
        if let Err(_) = io::stdin().read_line(&mut input_str) {
            continue;
        }

        match validator(&input_str) {
            Ok(value) => break value,
            Err(e) => {
                println!("{:?} {error}", e);
                input_str.clear();
            }
        }
    }
}

I invoke this function in main() to accept a float input as follows:

fn main() {
    let value: f64 = prompt_input(
        "Enter the temperature value you'd like to convert:",
        "Invalid numeric value",
        |raw_input| raw_input.trim().parse::<f64>(),
    );
    println!("You entered {value}");
}

This however doesn't compile. It says:
expected enum Result<_, Box<(dyn std::error::Error + 'static)>>
found enum Result<f64, ParseFloatError>
at the line inside main() beginning with |raw_input|.

Where am I going wrong and how do I get it work? Thanks!

It's not that the Ok has an f64, it's the error type. ParseFloatError doesn't get automatically converted to Box<dyn Error>.

Try:

- |raw_input| raw_input.trim().parse::<f64>(),
+ |raw_input| raw_input.trim().parse::<f64>().into(),
2 Likes

Ah, thanks for the pointer. I didn't realize that the conversion isn't automatic. Found this as well that explained a bit more.

What worked is:

|raw_input| raw_input.trim().parse::<f64>().map_err(|e| e.into()),
1 Like

ah, shoot, that's what I meant

1 Like

You should be able to do .map_err(Into::into) or .map_err(From::from), as well

2 Likes

It would be a mistake to transitively convert arbitrary type parameters to trait objects at arbitrary depths inside a compound type. Automatic conversions are in general undesirable. Rust's automatic conversions are therefore always limited to the simplest and direct cases, in fundamental types. I.e., you can convert a &T to a &dyn Trait for example, but not a Result<MyType<Nested<T>>> to a Result<MyType<Nested<dyn Trait>>>.

Also, you might be confusing the "automatic" conversion of errors with the ? operator, which does insert a From::from() call in the Err case.


  • Unrelated to the problem, but it would be more idiomatic to expect a FromStr bound instead of requiring a redundant closure.
  • It's also quite weird how you mix inline and stand-alone format arguments in println!().
  • Pattern matching against Err is superfluous if you only need to check the variant, it's better written as .is_err().
  • Formatting a user-facing error is better done through Display; the Debug format is usually not suited for end-user consumption.
  • Finally, you should use eprintln!() for printing error messages to the standard error stream, instead of stdout.

All in all, I'd write this as follows:

fn prompt_input<T>(prompt: &str, error_msg: &str) -> T
where
    T: FromStr,
    T::Err: Display,
{
    let mut input_str = String::new();
    let stdin = std::io::stdin();

    loop {
        input_str.clear();
        println!("{prompt}");

        if stdin.read_line(&mut input_str).is_err() {
            continue;
        }

        match input_str.trim().parse() {
            Ok(value) => break value,
            Err(parse_error) => {
                eprintln!("{error_msg}: {parse_error}");
            }
        }
    }
}
2 Likes

Thank you for the detailed feedback, much appreciated! I will incorporate all those points. Your version makes the calling of the function much simpler as well.

T: FromStr,
T::Err: Display,

This is quite clever. I didn't realize you could specify a second, deeper trait bound based on the first, shallow-er trait bound. Nice!