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 it 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")); } The output is the following: Ok(3) Err(ParseIntError { kind: InvalidDigit }) 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>()? } the `?` operator can only be used in a function that returns `Result` or `Option` 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 } Define Result variables Manually checks if any of the variables contains an error, i.e., the parsing failed 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 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 } Enable the experimental feature Use the ? operator though the defining function doesn't return Result Check for errors only once Unwrap confidently Alas, the code doesn't compile: the `?` operator can only be applied to values that implement `Try` 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() } } The compiler cannot infer the type Using ? on Result inside the try block is now allowed 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: Error management in Rust, and libs that support it "The Rust Unstable Book: try_blocks" The Rust RFC Book Extending Rust's Effect System I wrote previously about libs for error management in Rust . This week, I want to write about the try block, an experimental feature. libs for error management in Rust try 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 it to hook into a function call that returns a Result : ? ? 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. If the Result contains a value, it continues normally Result If it contains an error, it short-circuits and returns the Result to the calling function. Result 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")); } 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")); } The output is the following: Ok(3) Err(ParseIntError { kind: InvalidDigit }) Ok(3) Err(ParseIntError { kind: InvalidDigit }) Note that the defining function's signature must return a Result or an Option . The following block doesn't compile: must Result Option fn add(str1: &str, str2: &str) -> i8 { str1.parse::<i8>()? + str2.parse::<i8>()? } fn add(str1: &str, str2: &str) -> i8 { str1.parse::<i8>()? + str2.parse::<i8>()? } the `?` operator can only be used in a function that returns `Result` or `Option` the `?` operator can only be used in a function that returns `Result` or `Option` The verbose alternative We must manually unwrap to return a non-wrapper type, e.g. , i8 instead of Option<i8> . e.g. i8 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 } 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 } Define Result variables Manually checks if any of the variables contains an error, i.e., the parsing failed 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 Unwrap with confidence Define Result variables Define Result variables Result Manually checks if any of the variables contains an error, i.e., the parsing failed Manually checks if any of the variables contains an error, i.e. , the parsing failed i.e. 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 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 Result Unwrap with confidence Unwrap with confidence The try block to the rescue try 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: try experimental #![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 } #![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 } Enable the experimental feature Use the ? operator though the defining function doesn't return Result Check for errors only once Unwrap confidently Enable the experimental feature Use the ? operator though the defining function doesn't return Result ? Result Check for errors only once Unwrap confidently Alas, the code doesn't compile: the `?` operator can only be applied to values that implement `Try` the `?` operator can only be applied to values that implement `Try` 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 . i8 Try i8 Try Try Result Option Poll 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() } } 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() } } The compiler cannot infer the type Using ? on Result inside the try block is now allowed The compiler cannot infer the type Using ? on Result inside the try block is now allowed ? Result try 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 . try 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. ? Result Result try To go further: To go further: Error management in Rust, and libs that support it "The Rust Unstable Book: try_blocks" The Rust RFC Book Extending Rust's Effect System Error management in Rust, and libs that support it Error management in Rust, and libs that support it "The Rust Unstable Book: try_blocks" "The Rust Unstable Book: try_blocks" The Rust RFC Book The Rust RFC Book Extending Rust's Effect System Extending Rust's Effect System