DEV Community

Cover image for The try block in Rust
Nicolas Fränkel
Nicolas Fränkel

Posted on • Originally published at blog.frankel.ch

The try block in Rust

I wrote previously about libs for error management in Rust. This week, I want to write about the try block, an experimental feature.

The limit of the ? operator

Please check the above article for a complete refresher on error management in general and the ? operator in particular. In short, ? allows to hook into a function call that returns a Result:

  • If the Result contains a value, it continues normally
  • If it contains an error, it short-circuits and returns the Result to the calling function.
fn add(str1: &str, str2: &str) -> Result<i8, ParseIntError> {
  Ok(str1.parse::<i8>()? + str2.parse::<i8>()?)
}

fn main() {
    print!("{:?}", add("1", "2"));
    print!("{:?}", add("1", "a"));
}
Enter fullscreen mode Exit fullscreen mode

The output is the following:

Ok(3)
Err(ParseIntError { kind: InvalidDigit })
Enter fullscreen mode Exit fullscreen mode

Note that the defining function's signature must return a Result or an Option. The following block doesn't compile:

fn add(str1: &str, str2: &str) -> i8 {
  str1.parse::<i8>()? + str2.parse::<i8>()?
}
Enter fullscreen mode Exit fullscreen mode
the `?` operator can only be used in a function that returns `Result` or `Option`
Enter fullscreen mode Exit fullscreen mode

The verbose alternative

We must manually unwrap to return a non-wrapper type, e.g., i8 instead of Option<i8>.

fn add(str1: &str, str2: &str) -> i8 {
  let int1 = str1.parse::<i8>();                  //1
  let int2 = str2.parse::<i8>();                  //1
  if int1.is_err() || int2.is_err() { -1 }        //2-3
  else { int1.unwrap() + int2.unwrap() }          //4
}
Enter fullscreen mode Exit fullscreen mode
  1. Define Result variables
  2. Manually checks if any of the variables contains an error, i.e., the parsing failed
  3. Return a default value since we cannot get a Result. In this case, it's not a great idea, but it's for explanation's sake
  4. Unwrap with confidence

The try block to the rescue

The sample above works but is quite lengthy. The try block is an experimental approach to make it more elegant. It allows "compacting" all the checks for errors in a single block:

#![feature(try_blocks)]                           //1

fn add(str1: &str, str2: &str) -> i8 {
  let result = try {
    let int1 = str1.parse::<i8>();
    let int2 = str2.parse::<i8>();
    int1.unwrap()? + int2.unwrap()?               //2
  };
  if result.is_err() { -1 }                       //3
  else { result.unwrap() }                        //4
}
Enter fullscreen mode Exit fullscreen mode
  1. Enable the experimental feature
  2. Use the ? operator though the defining function doesn't return Result
  3. Check for errors only once
  4. Unwrap confidently

Alas, the code doesn't compile:

the `?` operator can only be applied to values that implement `Try`
Enter fullscreen mode Exit fullscreen mode

i8 doesn't implement Try. Neither i8 nor Try belong to our crate; a custom implementation would require the use of the wrapper-type pattern. Fortunately, a couple of types already implement Try: Result, Option, Poll, and ControlFlow.

fn add(str1: &str, str2: &str) -> i8 {
  let result: Result<i8, ParseIntError> = try {   //1
    str1.parse::<i8>()? + str2.parse::<i8>()?     //2
  };
  if result.is_err() { -1 }
  else { result.unwrap() }
}
Enter fullscreen mode Exit fullscreen mode
  1. The compiler cannot infer the type
  2. Using ? on Result inside the try block is now allowed

Diagram of the Try API

Conclusion

I learned about the try block in Java over twenty years ago. Java needs it because exceptions are at the root of its error-handling system; Rust doesn't because it uses Functional Programming for its error handling - mainly Result.

The ? operator builds upon the Result type to allow short-circuiting in functions that return Result themselves. If the function doesn't, you need a lot of boilerplate code. The experimental try block relieves some of it.

To go further:


Originally published at A Java Geek on April 21st, 2024

Top comments (6)

Collapse
 
michaeltharrington profile image
Michael Tharrington

Nice resource, Nicolas! Appreciate ya sharing with us.

Just gotta note that seeing the words "try block" instantly made me think of this Yoda quote:

Yoda saying "Do or do not. There is no try."

Collapse
 
nfrankel profile image
Nicolas Fränkel

Thanks for your kind words.

As of try, it exists in a couple of languages already 😉

Collapse
 
michaeltharrington profile image
Michael Tharrington

You're very welcome! 😀

And ah I see... interesting! I see your bit at the end about learning this in Java twenty years ago... do you know if that's the language where it first originated? I imagine a lot of languages have taken influence from those that came before.

Thread Thread
 
nfrankel profile image
Nicolas Fränkel

That's the best I could find: stackoverflow.com/questions/610262...

Collapse
 
tolik518 profile image
Anatolij Vasilev

The try-block feels like an anti-pattern since you just wrap all your problems away instead of dealing with them.
This is a huge problem in most languages so I'm a big fan that the rusty way is to use the Result instead.

Collapse
 
nfrankel profile image
Nicolas Fränkel

I disagree, it's syntactic sugar for code that would be much verbose without it.