2
\$\begingroup\$

I've recently started learning Rust, and I've managed to whip up this function, which reads and parses a thing from stdin. I don't like it though, and I feel it could be further simplified. What do you think?

fn read_thing<T: str::FromStr>() -> Result<T, &'static str> {
    let mut buffer = String::new();
    match io::stdin().read_line(&mut buffer) {
        Ok(_)  => match buffer.trim().parse() {
            Ok(n)  => Ok(n),
            Err(_) => Err("Failed parsing input.")
        },
        Err(_) => Err("Failed reading from stdin."),
    }
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Here's a few possibilities. They all rely on the fact that Result can be mapped over, both in the success and failure cases.

Additionally, I personally detest having trait bounds on generics inside the generic declaration. I almost always move them to a where clause for readability.


fn read_thing<T>() -> Result<T, &'static str>
    where T: str::FromStr
{
    let mut buffer = String::new();
    try!(io::stdin().read_line(&mut buffer).map_err(|_| "Failed reading from stdin."));
    buffer.trim().parse().map_err(|_| "Failed parsing input.")

This is the most direct modification to your existing code. It transforms the errors into the desired strings and uses the try! macro to return immediately on the first error.


use std::io::BufRead;

fn read_thing<T>() -> Result<T, &'static str>
    where T: str::FromStr
{
    let stdin = io::stdin();
    let v = stdin.lock().lines().next()
        .ok_or("No data to read")
        .and_then(|s| s.map_err(|_| "Failed reading from stdin"))
        .and_then(|s| s.trim().parse().map_err(|_| "Failed parsing input"));
    v
}

This was an attempt to chain it all together. It almost works nicely, but introduces a new error condition - what if stdin is fine, there's just nothing to read? Add to that the lifetime shenanigans that involve locking the standard in, and this isn't the nicest either.


fn one_line_from_stdin() -> io::Result<String> {
    let mut buffer = String::new();
    io::stdin().read_line(&mut buffer).map(|_| buffer)
}

fn read_thing2<T>() -> Result<T, &'static str>
    where T: str::FromStr
{
    one_line_from_stdin()
        .map_err(|_| "Failed reading from stdin")
        .and_then(|s| s.trim().parse().map_err(|_| "Failed parsing input"))
}

Continuing on a quest to chain it together, I extracted a method that just returns a single string. It's probably not very efficient, but reads OK.


fn read_thing2<T>() -> Result<T, &'static str>
    where T: str::FromStr
{
    let mut buffer = String::new();
    io::stdin().read_line(&mut buffer).map(|_| buffer)
        .map_err(|_| "Failed reading from stdin")
        .and_then(|s| s.trim().parse().map_err(|_| "Failed parsing input"))

Same idea as the previous code, this just inlines the input method.


use std::error::Error;

fn read_thing<T>() -> Result<T, Box<Error>>
    where T: str::FromStr,
          T::Err: Error + 'static,
{
    let mut buffer = String::new();
    try!(io::stdin().read_line(&mut buffer));
    let v = try!(buffer.trim().parse());
    Ok(v)
}

This is what I'd probably write to start with. I find the messages from most errors to be fairly readable and understandable, but I'm not showing them to an end user most of the time. I'd just use a little elegance baked into the try! macro and the From implementation of Box<Error> to pass back the original errors, abstracted out to the generic concept of an error. More information can be found in The Rust Programming Language.

If I cared more about custom messages or efficiency, I'd upgrade to a custom error type. I've also recently discovered the quick_error crate which provides a macro to make custom types easier, but haven't had a chance to try it out yet.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.