Proper Error propagation in Rust

This seems to be a simple program, where most of the errors should result in an error message, and that's mostly it. It's not a library that you expend hundreds of people to use, and get information from them.

Given that assumption, there are two easy recommendations that will at least help you get the job done, and advance you further in your learning of Rust.

  1. Rust has the ? operator for "if this is an error, return it." I personally consider this one of its best features. Several of the places you have unwrap can use it directly.
  2. The std::io::Error has an error kind called InvalidInput, which accompanies an error message. This seems like what you would want if a parse fails. The error types generated by a failure to parse integers, floats, and strings are not the same as io::Error, but they are easy to "convert" with an error message that suits your purpose.

With these two suggestions applied, your code would look like this. It is not "idiomatic" per se, but it does handle the errors more cleanly.

pub fn func2_read_file(max_line:usize) -> Result<Vec<DataStruct>, io::Error>{
    let file = File::open(filename)?; // return the error, otherwise continue on.
    let reader = BufReader::new(file);

    let mut vec_data:Vec<DataStruct>=Vec::new();
    for (index, line) in reader.lines().enumerate() {
        let line = line.map_err(|e| io::Error(io::ErrorKind::InvalidInput, "failed to read a string on a line"))?;

        let split = line.split("\t");
        let vec = split.collect::<Vec<&str>>();
        vec_data.push(DataStruct {
            id: vec[0].parse::<i32>()
                .map_err(|_| io::Error(io::ErrorKind::InvalidInput, "failed to parse column 1 as i32"))?; // map the result into a Result<T, io::Error>, and then perform the ? operator as above
            name_acsii: vec[2].to_string(),
            latitude: vec[4].parse::<f32>().map_err(|_| io::Error(io::ErrorKind::InvalidInput, "failed to parse column 4 as f32"))?,
            longitude: vec[5].parse::<f32>().map_err(|_| io::Error(io::ErrorKind::InvalidInput, "failed to parse column 5 as f32"))?,
        });        
        if index>max_line{
            break;
        }
    }
    Ok(vec_data) // idiomatic return style, plus it's a Result<Vec, E> not a Vec
}

With this design, however, you cannot do things like indicate line numbers of an error. You could format! an error message with index in it, but this type of information is usually conveyed better with a custom error type, or returning the original error types instead.

For returning multiple error types, you can use the anyhow crate. It will "fold together" all your errors, returning a single structure that makes operations like pretty-printing easy.

I would also recommend a recent thread or understanding errors in Rust.

As a broader statement, I would also suggest looking into the nom crate for parsing things from a stream or file. It is somewhat verbose, but structures your code to be efficient (as a recursive descent parser based on rules), and handle errors gracefully.

Hope this helps!

EDIT: clarity and extra suggestion

4 Likes