Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do proper error handling inside a map function? [duplicate]

Tags:

rust

i want to read a textfile and convert all lines into int values. I use this code. But what i really miss here is a "good" way of error handling.

use std::{
    fs::File,
    io::{prelude::*, BufReader},
    path::Path
};

fn lines_from_file(filename: impl AsRef<Path>) -> Vec<i32> {
    let file = File::open(filename).expect("no such file");
    let buf = BufReader::new(file);
    buf.lines()
        .map(|l| l.expect("Could not parse line"))
        .map(|l:String| l.parse::<i32>().expect("could not parse int"))
        .collect()
}

Question: How to do proper error handling ? Is this above example "good rust code" ? or should i use something like this :

fn lines_from_file(filename: impl AsRef<Path>) -> Vec<i32> {
    let file = File::open(filename).expect("no such file");
    let buf = BufReader::new(file);
    buf.lines()
        .map(|l| l.expect("Could not parse line"))
        .map(|l:String| match l.parse::<i32>() {
            Ok(num) => num,
            Err(e) => -1 //Do something here 
        }).collect()
}
like image 525
Skary Avatar asked Jun 15 '26 18:06

Skary


1 Answers

You can actually collect into a Result<T, E>. See docs

So you could collect into a Result<Vec<i32>, MyCustomErrorType>. This works when you transform your iterator in an iterator which returns a Result<i32, MyCustomErrorType>. The iteration stops at the first Err you map.

Here's your working code example. I used the thiserror crate for error handling

use std::{
    fs::File,
    io::{prelude::*, BufReader},
    num::ParseIntError,
    path::Path,
};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum LineParseError {
    #[error("Failed to read line")]
    IoError(#[from] std::io::Error),
    #[error("Failed to parse int")]
    FailedToParseInt(#[from] ParseIntError),
}

fn lines_from_file(filename: impl AsRef<Path>) -> Result<Vec<i32>, LineParseError> {
    let file = File::open(filename).expect("no such file");
    let buf = BufReader::new(file);
    buf.lines().map(|l| Ok(l?.parse()?)).collect()
}

Some small explanation of how the code works by breaking down this line of code:

buf.lines().map(|l| Ok(l?.parse()?)).collect()
  • Rust infers that we need to collect to a Result<Vec<i32>, LineParseError> because the return type of the function is Result<Vec<i32>, LineParseError>
  • In the mapping method we write l? this makes the map method return an Err if the l result contains an Err, the #[from] attribute on LineParseError::IoError takes care of the conversion
  • The .parse()? works the same way: #[from] on LineParseError::FailedToParseInt takes care of the conversion
  • Last but not least our method must return Ok(...) when the mapping does succeed, this makes the collect into a Result<Vec<i32>, LineParseError> possible.
like image 132
Jeroen Vervaeke Avatar answered Jun 17 '26 08:06

Jeroen Vervaeke



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!