42

Is it possible to handle multiple different errors at once instead of individually in Rust without using additional functions? In short: what is the Rust equivalent to a try-catch statement?

A feature like this (First-class error handling with ? and catch) was suggested back in 2016, but I can't tell what came out of it and how a 2019 solution for such a problem might look like.

For example, doing something like this:

try {
    do_step_1()?;
    do_step_2()?;
    do_step_3()?;
    // etc
} catch {
    alert_user("Failed to perform necessary steps");
}

Instead of:

match do_steps() {
    Ok(_) => (),
    _ => alert_user("Failed to perform necessary steps")
}

// Additional function:
fn do_steps() -> Result<(), Error>{
    do_step_1()?;
    do_step_2()?;
    do_step_3()?;
    // etc
    Ok(())
}

My program has a function which checks a variety of different places in the registry for different data values and returns some aggregate data. It would need to use many of these try-cache statements with try-catch inside of other try-catch inside of loops.

Shepmaster
  • 326,504
  • 69
  • 892
  • 1,159
Marc Guiselin
  • 3,084
  • 2
  • 24
  • 37

5 Answers5

42

There is no try catch statement in Rust. The closest approach is the ? operator.

However, you do not have to create a function and a match statement to resolve it in the end. You can define a closure in your scope and use ? operator inside the closure. Then throws are held in the closure return value and you can catch this wherever you want like following:

fn main() {
    let do_steps = || -> Result<(), MyError> {
        do_step_1()?;
        do_step_2()?;
        do_step_3()?;
        Ok(())
    };

    if let Err(_err) = do_steps() {
        println!("Failed to perform necessary steps");
    }
}

Playground

Is it possible to handle multiple different errors at once instead of individually in Rust without using additional functions?

There is a anyhow crate for the error management in Rust mostly recommended nowadays.

As an alternative, There is a failure crate for the error management in Rust. Using Failure, you can chain, convert, concatenate the errors. After converting the error types to one common type, you can catch (handle) it easily.

Akiner Alkan
  • 5,023
  • 25
  • 58
  • 2
    Just to chime in, but `failure` is not the only crate that helps with error management. There are many, each with different focus. – Shepmaster Apr 19 '19 at 14:48
  • 4
    Note that your closure expression is exactly [what the `try` block is intended to be used as](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=805b37002e0965c5d0705b7ab11acabd). – Shepmaster Apr 19 '19 at 14:51
  • The most recommended nowadays is `anyhow`. – rsalmei Oct 05 '21 at 21:25
  • @rsalmei, thanks for pointing out the newest updated crate for that, I have edited my answer as well ;) – Akiner Alkan Oct 06 '21 at 07:09
  • For my purposes, this tweak proved to be more useful: https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=4a2b222f2d83a3809b1fde6f8e827012 – Chad Mar 22 '22 at 18:06
  • Why don't the Rust devs just have every error class inherit from some Error trait by default, so Results could always be Result< something, Error>? That seems to be the way errors behave in every other OO language. – Randall Coding Mar 23 '22 at 20:56
  • The Error trait is already applied for all error types. However, you would like to be more precise about error management with different error types to handle different errors with different behaviors. This is also very similar in the other OO languages. Extra note, OOP is not the strongest side of the Rust language imho. It has other perks than OO such as concurrent programming ensuring thread safety etc. – Akiner Alkan Mar 24 '22 at 09:32
23

Results in Rust can be chained using and_then. So you can do this:

if let Err(e) = do_step_1().and_then(do_step_2).and_then(do_step_3) {
    println!("Failed to perform necessary steps");
}

or if you want a more compact syntax, you can do it with a macro:

macro_rules! attempt { // `try` is a reserved keyword
   (@recurse ($a:expr) { } catch ($e:ident) $b:block) => {
      if let Err ($e) = $a $b
   };
   (@recurse ($a:expr) { $e:expr; $($tail:tt)* } $($handler:tt)*) => {
      attempt!{@recurse ($a.and_then (|_| $e)) { $($tail)* } $($handler)*}
   };
   ({ $e:expr; $($tail:tt)* } $($handler:tt)*) => {
      attempt!{@recurse ($e) { $($tail)* } $($handler)* }
   };
}

attempt!{{
   do_step1();
   do_step2();
   do_step3();
} catch (e) {
   println!("Failed to perform necessary steps: {}", e);
}}

playground

Shepmaster
  • 326,504
  • 69
  • 892
  • 1,159
Jmb
  • 12,991
  • 20
  • 46
7

There's also an unstable feature called try_blocks (https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html, https://github.com/rust-lang/rust/issues/31436)

Usage example:

#![feature(try_blocks)]

fn main() {
    // you need to define the result type explicitly
    let result: Result<(), Error> = try {
        do_step_1()?;
        do_step_2()?;
        do_step_3()?;
    };

    if let Err(e) = result {
        println!("Failed to perform necessary steps, ({:?})", e);
    }
}

fn do_step_1() -> Result<(), Error> { Ok(()) }
fn do_step_2() -> Result<(), Error> { Ok(()) }
fn do_step_3() -> Result<(), Error> { Err(Error::SomeError) }

#[derive(Debug)]
enum Error {
    SomeError,
}
Mika Vatanen
  • 3,292
  • 1
  • 23
  • 30
0

I think match expression is equivalent of try/catch

match get_weather(location) {
   Ok(report) => {
                  display_weather(location, &report);
                 }
   Err(err) => {
                 println!("error querying the weather: {}", err);
                 // or write a better logic
   }
}

We try to get weather report from api, if our request fails, handle the error other wise shows the result.

Yilmaz
  • 1
  • 7
  • 79
  • 120
-1

The try and except concept is used in extremely vague terms. Since Rust is a strongly typed language, the user must write their own methods for handling errors by relying on the provided Option<T> and Result<T, E> enumerations or by defining their own accustomed enumerations.

See here for a more in-depth read for error-handling using enumerations.

The try macro is deprecated and has been replaced with the ? operator which makes it easier to organize and clean up error-handling because it can get messy. A main use for the ? operator would be that it allows you to implement the From trait for Result<T, E>'s Err(E) variant.

Here's a basic example:

use std::num::ParseIntError;

//  Custom error-based enum with a single example
#[derive(Debug)]
enum Error {
    ParseIntError(ParseIntError),
    //  Other errors...
}

//  Then implement the `From` trait for each error so that the `?` operator knows what to do for each specified error.

impl From<ParseIntError> for Error {
    fn from(error: ParseIntError) -> Self {
        Self::ParseIntError(error)
    }
}

//  When using the `?` try operator, if the `Result` is an `Err` then it will basically act as `return Err(E)` returning that error value out to the current scope.  If it is `Ok(T)`, it will simply unwrap the variant.

fn main() -> Result<(), Error> {
    //  This will return the value `69` as a `u8` type
    let parsed_value_1 = "69".parse::<u8>()?;
    println!("{}", parsed_value_1);

    //  Since parsing fails here, a `ParseIntError` will be returned to the current function.  *Since the scope is the `main` function, it will automatically print the error after panicking.
    let parsed_value_2 = "poop".parse::<u8>()?;

    //  Unreachable code
    println!("{}", parsed_value_2);
    Ok(())
}
splurf
  • 21
  • 3
  • 2
    The `try` keyword is reserved for future use, not deprecated, and has little connection to the (deprecated) `try!` macro, which has been superseded by the `?` operator. – L. F. Aug 06 '21 at 11:47