Rust: Read and map lines from stdin and handling different error types

时光毁灭记忆、已成空白 提交于 2020-01-16 05:36:07

问题


I'm learning Rust and trying to solve some basic algorithm problems with it. In many cases, I want to read lines from stdin, perform some transformation on each line and return a vector of resulting items. One way I did this was like this:

    // Fully working Rust code
    let my_values: Vec<u32> = stdin
        .lock()
        .lines()
        .filter_map(Result::ok)
        .map(|line| line.parse::<u32>())
        .filter_map(Result::ok)
        .map(|x|x*2) // For example
        .collect();

This works but of course silently ignores any errors that may occur. Now what I woud like to do is something along the lines of:

    // Pseudo-ish code
    let my_values: Result<Vec<u32>, X> = stdin
        .lock()
        .lines() // Can cause std::io::Error
        .map(|line| line.parse::<u32>()) // Can cause std::num::ParseIntError
        .map(|x| x*2)
        .collect();

Where X is some kind of error type that I can match on afterwards. Preferably I want to perform the whole operation on one line at a time and immediately discard the string data after it has been parsed to an int.

I think I need to create some kind of Enum type to hold the various possible errors, possibly like this:

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

However, I don't quite understand how to put everything together to make it clean and avoid having to explicitly match and cast everywhere. Also, is there some way to automatically create these enum error types or do I have to explicilty enumerate them every time I do this?


回答1:


You're on the right track. The way I'd approach this is by using the enum you've defined, then add implementations of From for the error types you're interested in. That will allow you to use the ? operator on your maps to get the kind of behaviour you want.

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

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

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

Then you can implement the actual transform as either

let my_values: Vec<_> = stdin
    .lock()
    .lines()
    .map(|line| -> Result<u32,MyError> { Ok(line?.parse::<u32>()?*2) } )
    .collect();

which will give you one entry for each input, like: {Ok(x), Err(MyError(x)), Ok(x)}. or you can do:

let my_values: Result<Vec<_>,MyError> = stdin
    .lock()
    .lines()
    .map(|line| -> Result<u32,MyError> { Ok(line?.parse::<u32>()?*2) } )
    .collect();

Which will give you either Err(MyError(...)) or Ok([1,2,3])

Note that you can further reduce some of the error boilerplate by using an error handling crate like snafu, but in this case it's not too much.



来源:https://stackoverflow.com/questions/59187274/rust-read-and-map-lines-from-stdin-and-handling-different-error-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!