Error handling in Rust
About This Module
This module covers error handling in Rust, focusing on the use of the Result enum for recoverable errors and the panic! macro for unrecoverable errors. You'll learn how to propagate errors using the ? operator and how to design functions that can gracefully handle failure scenarios while maintaining Rust's safety and performance guarantees.
Prework
Prework Reading
Please read the following sections from The Rust Programming Language Book:
- Chapter 9: Error Handling
- Chapter 9.1: Unrecoverable Errors with panic!
- Chapter 9.2: Recoverable Errors with Result
Pre-lecture Reflections
Before class, consider these questions:
- What are the differences between recoverable and unrecoverable errors in Rust?
- How does the
Resultenum facilitate error handling in Rust? - What are the advantages of using the
?operator for error propagation? - When should you use
panic!versus returning aResult? - How does Rust's approach to error handling compare to exception handling in other languages?
Lecture
Learning Objectives
By the end of this module, you will be able to:
- Understand the difference between recoverable and unrecoverable errors
- Use the
panic!macro for handling unrecoverable errors - Use the
Resultenum for handling recoverable errors - Propagate errors using the
?operator - Design functions that can handle errors gracefully
Error Handling in Rust
Two basic options:
-
terminate when an error occurs: macro
panic!(...) -
pass information about an error: enum
Result<T,E>
Macro panic!(...)
- Use for unrecoverable errors
- Terminates the application
fn divide(a:u32, b:u32) -> u32 { if b == 0 { panic!("I'm sorry, Dave. I'm afraid I can't do that."); } a/b } fn main() { println!("{}", divide(20,7)); //println!("{}", divide(20,0)); // Try uncommenting this line }
Enum Result<T,E>
Provided by the standard library, but shown here for reference.
#![allow(unused)] fn main() { enum Result<T,E> { Ok(T), Err(E), } }
Functions can use it to
- return a result
- or information about an encountered error
fn divide(a:u32, b:u32) -> Result<u32, String> { if b != 0 { Ok(a / b) } else { let str = format!("Division by zero {} {}", a, b); Err(str) } } fn main() { println!("{:?}", divide(20,7)); println!("{:?}", divide(20,0)); }
- Useful when the error best handled somewhere else
- Example: input/output subroutines in the standard library
Common pattern: propagating errors
- We are interested in the positive outcome:
tinOk(t) - But if an error occurs, we want to propagate it
- This can be handled using
matchstatements
fn divide(a:u32, b:u32) -> Result<u32, String> { if b != 0 { Ok(a / b) } else { let str = format!("Division by zero {} {}", a, b); Err(str) } } // compute a/b + c/d fn calculate(a:u32, b:u32, c:u32, d:u32) -> Result<u32, String> { let first = match divide(a,b) { Ok(t) => t, Err(e) => return Err(e), }; let second = match divide(c,d) { Ok(t) => t, Err(e) => return Err(e), }; Ok(first + second) } fn main() { println!("{:?}", calculate(16,4,18,3)); println!("{:?}", calculate(16,0,18,3)); }
The question mark shortcut
-
Place
?after an expression that returnsResult<T,E> -
This will:
- give the content of
Ok(t) - or immediately return the error
Err(e)from the encompassing function
- give the content of
fn divide(a:u32, b:u32) -> Result<u32, String> { if b != 0 { Ok(a / b) } else { let str = format!("Division by zero {} {}", a, b); Err(str) } } // compute a/b + c/d fn calculate(a:u32, b:u32, c:u32, d:u32) -> Result<u32, String> { Ok(divide(a,b)? + divide(c,d)?) } fn main() { println!("{:?}", calculate(16,4,18,3)); println!("{:?}", calculate(16,0,18,3)); }
Optional: try/catch pattern
- In some languages we have the pattern
try/catchorthrow/catchortry/except(C++, Java, Javascript, Python). - Rust does not have something equivalent
The Rust pattern for error handling is the following:
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");
}
- Create a closure with the code you want to guard. Use the ? shorthand inside the closure for anything that can return an Error. Use a match or if let statement to catch the error.
Recap
- Use
panic!for unrecoverable errors - Use
Result<T,E>for recoverable errors - Use
?to propagate errors