58

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 similar feature was suggested back in 2016, but I don't know what came of it.

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.

Aryan Beezadhur
  • 4,503
  • 4
  • 21
  • 42
Marc Guiselin
  • 3,442
  • 2
  • 25
  • 37

8 Answers8

61

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");
    }
}

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

anyhow is an error handling crate in Rust and is recommended nowadays.

Aryan Beezadhur
  • 4,503
  • 4
  • 21
  • 42
Akiner Alkan
  • 6,145
  • 3
  • 32
  • 68
  • 6
    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
  • 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
31

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

Jmb
  • 18,893
  • 2
  • 28
  • 55
12

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,782
  • 1
  • 28
  • 33
5

Not sure it's considered idiomatic Rust, but you can use an anonymous closure to achieve syntax similar to try/catch:

fn do_step_1() -> Result<(), String> { Ok(()) }
fn do_step_2() -> Result<(), String> { Err("error at step 2".to_string()) }
fn do_step_3() -> Result<(), String> { Ok(()) }
fn alert_user(s: &str) { println!("{}", s); }

fn main() {
    (|| {
        do_step_1()?;
        do_step_2()?;
        do_step_3()?;
        Ok(())
    })().unwrap_or_else(|_err: String| {
        alert_user("Failed to perform the necessary steps");
    })
}
Olivier Lalonde
  • 19,423
  • 28
  • 76
  • 91
  • can you also put finally block? – chendu Mar 02 '23 at 16:10
  • @chendu Just put code after this structure. The code inside of the try and catch parts of this can't cause the outer function to exit early, so there's no need for special finally block handling. – Macil Apr 02 '23 at 22:21
2

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
  • 35,338
  • 10
  • 157
  • 202
  • Not an equivalent, but this is the way rust does it. A `try` will flow to its `catch` block if ANY statement inside the `try` block throws an exception. In rust, using this approach, we need to check each statement for an `Err`. – lmat - Reinstate Monica Jan 30 '23 at 21:53
0

Library functions returning different error types further complicate our code, especially if we try to design our function to return a Result.

I would suggest using ok(), which converts a Result into an Option (translating a potential Err into None), combined with the ? operator, which unwraps a valid value, or else ends our function returning None.

The obvious drawback is that we lose knowledge of what has gone wrong.

It's worth pointing out that Rust doesn't need a finally clause because clean-up code is always executed despite a failing ?.

Also note that ? can't be used in the main() function. There, we need unwrap(), which calls panic() in case of an error.

In order to showcase this, consider the following code that reads a JSON file and parses it, dealing with different types of errors.

use std::fs;
use serde_json::{Value};

// --------------------------------------------------------
// file_name: Text -> () -> Some( ObjectJson ) | None
// --------------------------------------------------------
fn read_file( file_name: String ) -> Option<Value> {

    let contents : String = fs::read_to_string( file_name ).ok() ? ;  // read the file

    let obj : Value = serde_json::from_str( & contents ).ok() ? ; // convert the text into a JSON object

    Some( obj )

} // ()

// --------------------------------------------------------
// --------------------------------------------------------
fn main() {

    let json_obj = read_file( "data.json".to_string() ).unwrap();

    println!( "{:}", json_obj[0] );

}
cibercitizen1
  • 20,944
  • 16
  • 72
  • 95
0

It is worth noting that in an async context, async block (i.e. async {}) works exactly like try-catch, and this is in stable Rust. It would capture return values, including those propagated by ?, just like with functions and closures:

#[tokio::main]
async fn main() {
    let res = async {
        None?;
        Some(1)
    };
    assert_eq!(res.await, None);
}
nirvana-msu
  • 3,877
  • 2
  • 19
  • 28
-2

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
  • 19
  • 3
  • 3
    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