Alternative to try..catch..finally in Rust

Many languages have try.. catch.. finally.. method to catch errors and it is a good general method for picking up errors. What is Rust's general error catching method?

I checked the net and found a number of error catching methods in Rust. For example, use of ? and .expect() and returning Result etc. What general method in Rust can be applied to most situations? Thanks for your insight.

Rust uses Result<T, E> for error handling, and the ? operator for unwrapping the data or propagating the error to the caller. There's a lot more to say about it, but the details are not entirely interesting until you have a need to get into them.

The major concern is that Result is what you want and there is no exception handling facility [1] in the language.

See also:

  1. Error Handling - The Rust Programming Language (rust-lang.org)
  2. Error Handling in Rust - Andrew Gallant's Blog (burntsushi.net)

  1. std::panic::catch_unwind exists, but it is not for error handling. ↩︎

4 Likes

I find using ? as the easiest, as mentioned one of the links mentioned above.

use std::fs::File;
use std::io::Read;
use std::path::Path;

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, String> {
    let mut file = File::open(file_path).map_err(|e| e.to_string())?;
    let mut contents = String::new();
    file.read_to_string(&mut contents).map_err(|e| e.to_string())?;
    let n = contents.trim().parse::<i32>().map_err(|e| e.to_string())?;
    Ok(2 * n)
}

fn main() {
    match file_double("foobar") {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

What if I just use ? instead of .map_err(|e| e.to_string())? ?

How will following code work if error occurs:

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, String> {
    let mut file = File::open(file_path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let n = contents.trim().parse::<i32>()?;
    Ok(2 * n)
}

The expr? basically expands to

match expr {
    Ok(ok) => ok,
    Err(err) => return Err(err.into()),
}

So the error type of the Result needs to implement Into<T> where T is the type of the error produced by the expression.

So you can't use String directly since there isn't an impl Into<String> for std::io::Error[1], but you can create an error type that works the same way pretty easily

Playground

use std::{
    fmt::Display,
    fs::File,
    io::{Read, Write},
    path::Path,
};

struct StringError(String);

impl<E> From<E> for StringError
where
    E: Display,
{
    fn from(value: E) -> Self {
        Self(value.to_string())
    }
}

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, StringError> {
    let mut file = File::open(file_path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let n = contents.trim().parse::<i32>()?;
    Ok(2 * n)
}

fn main() {
    let mut file = File::options()
        .create(true)
        .write(true)
        .truncate(true)
        .open("foobar")
        .unwrap();
    file.write_all(b"12").unwrap();

    match file_double("foobar") {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err.0),
    }
}

  1. or a corresponding From impl ↩︎

2 Likes

There are also some popular third-party libraries for error handling. thiserror and anyhow are widely used.

2 Likes

It compiles if you use Box<dyn Error>:

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, Box<dyn Error>> {
    let mut file = File::open(file_path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let n = contents.trim().parse::<i32>()?;
    Ok(2 * n)
}

Box<dyn Error> seems interesting. How can I read the error returned by it from calling function? Will following function work?

fn main() {
    match file_double("foobar") {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

You can try it for yourself on the Playground.


One thing that nobody mentioned explicitly is that there's no equivalent of finally built into error handling, because Rust doesn't need it. Destructors run even when an error makes ? return early, so automated resource cleanup works just fine.

1 Like

Very interesting point re finally!

This forum is very informative and friendly! Thanks to all for your insights.

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.