What does the best way to avoid nested IFs with Result or Option?

Situation: You're implementing a method, that requires invocation of multiple functions, which returns Result <>. You have to handle all errors. There are a few ways to handle the situation. Let's say you handle 2 files and you don't need to crash the application with panic!, But handle it.

The solution with match :

fn something(path1: &String, path2: &String) -> Result<String, Error> {
    match File::open(path1) {
        Ok(mut file1) => {
            match File::open(path2) {
                Ok(mut file2) => Ok(),
                Err() => Err(),
            }
        },
        Err() => Err(),
    }
}

The solution with if let ..

fn perform(path1: &String, path2: &String) -> Result<String, Error> {
    if let Ok(mut file1) = File::open(path1) {
        if let (Ok(mut file1)) = File::open(path2) {
            // handeling
            Ok()
        } else {
            Err()
        }
    } else {
        Err()
    }
}

The solution with guards:

fn something(path1: &String, path2: &String) -> Result<String, Error> {
    let file1_result = File::open(path1);
    if let Err(error) = file1_result {
        return Err();
    }

    let file2_result = File::open(path2);
    if let Err(error) = file2_result {
        return Err();
    }

    let file1 = file2_result.unwrap();
    let file2 = file2_result.unwrap();

    // handeling files
    Ok()
}

It looks like Rust tends to have a high level of nested IFs because of the broad usage of Result and Option. By themselves, they are handy tools, but applying them right is not trivial.

How do you prefer to handle this situation in Rust? Maybe do you know other ways to handle it? Would be great to know!

The solution is the question mark operator:

fn something(path1: &String, path2: &String) -> Result<String, Error> {
    let file1 = File::open(path1)?;
    let file2 = File::open(path2)?;
    
    // ...
}
13 Likes

Another options is to use the .and_then associated function:

fn something(path1: &String, path2: &String) -> Result<String, Error> {
    File::open(path1)
        .and_then(|f1| File::open(path2).map(|f2| (f1, f2)))
        .map(|(file1, file2)| {
            // ...
        })
        .map_err(|e| { /* You might want to convert std::io::Error into Error */ })
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.