Error Handling
Trigger Irrecoverable Panics
The panic!(...)
macro allows a program to terminate immediately and provide feedback to the caller of the program.
/// This example demonstrates a simple panic. /// When executed, it will cause the program to terminate abruptly. fn main() { panic!("Crash and burn"); }
Generate and Handle Recoverable Errors with Result
Result
is an enum used to represent the outcome of operations that might fail. It is a flexible way to handle errors in a type-safe manner. The enum has two variants: Ok
and Err
.
enum Result<T, E> { Ok(T), Err(E), } // T and E are generic.
Simply return one of the two variants as needed. Note that Result
is in the prelude.
fn divide_numbers(x: i32, y: i32) -> Result<i32, &'static str> { if y == 0 { Err("Division by zero") } else { Ok(x / y) } }
You can handle the Result
via a simple match
or if let
expression:
fn main() { let _result1: Result<i32, &str> = Ok(10); let result2: Result<i32, &str> = Err("Something went wrong."); // Here, we just print the status. match result { Ok(value) => println!("Success: {}", value), Err(error) => eprintln!("Error: {}", error), } }
Convert a Result
or Option
into an Irrecoverable Panic
panic!
is closely tied with the unwrap
method of both Option
and Result
enums. Both implementations call panic!
when they are set to the None
or Err
variants. expect
is frequently used instead of unwrap
.
#![allow(clippy::unnecessary_literal_unwrap)] //! Demonstrates the use of `unwrap()` and `expect()` for handling `Result` //! types. fn main() { let number_str = "42"; // `parse()` attempts to convert the string into a number. // This operation can fail if the string isn't a valid number, so it returns // a `Result<u32, ParseIntError>`. let result: Result<u32, std::num::ParseIntError> = number_str.parse(); // If the error is not recoverable at this point, we can cause the // program to crash if this instance of `Result` is an `Err` value: // 1. `unwrap()` can be called on the `Result` to extract the `u32` value. // - If the parsing is successful, the value is assigned to `number`. // - If the parsing fails, the program panics with an error message. let number: u32 = result.unwrap(); println!("The number is: {}", number); // 2. `expect` is similar (and often preferred) to `unwrap`. // If `Result` is an `Err` value, it panics and displays the message // passed as an argument. let result: Result<i32, &str> = Ok(10); result.expect("Failed to do something."); // You may also use `unwrap`, which panics if there is an error but does not // display a custom message: `result.unwrap();` }
Provide a Fallback Value with unwrap_or
or unwrap_or_else
//! Demonstrates the use of `unwrap_or` and `unwrap_or_else` for handling //! `Option` and `Result` types. //! //! `unwrap_or` provides a default value if the `Option` is `None` or the //! `Result` is `Err`. `unwrap_or_else` provides a closure to compute a default //! value in the same scenarios. #![allow(clippy::unnecessary_literal_unwrap)] use std::fs; use std::fs::File; use std::io::ErrorKind; fn main() { // Use `unwrap_or` to provide a default value if `maybe_value` is `None`: let maybe_value: Option<i32> = None; let value: i32 = maybe_value.unwrap_or(0); println!("Value: {}", value); // Prints zero. // Use `unwrap_or` to provide a default value if a `Result` is `Err`. let result: Result<i32, &str> = Err("Something went wrong"); let value: i32 = result.unwrap_or(42); println!("Value: {}", value); // Prints 42. // Use `unwrap_or_else` to compute a default value (within a closure) // when an `Option` is `None` or `Result` an `Err`. let some_option: Option<&str> = None; let value: &str = some_option.unwrap_or_else(|| { println!("Option was None. Providing default value."); "default_value" }); println!("Value: {}", value); // Fallback example: try to open a file; if the file was not found, try to // create the file. Otherwise, panic. let _greeting_file = File::open("temp/hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("temp/hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } }); }
Chain operations with and_then
and_then
is used to chain the operations. It runs the next function only if the previous Result
is Ok
. If any step produces an Err
, the chain stops, and the Err
is returned.
fn divide_by_two(x: i32) -> Result<i32, &'static str> { if x % 2 == 0 { Ok(x / 2) } else { Err("Not divisible by 2") } } fn divide_by_three(x: i32) -> Result<i32, &'static str> { if x % 3 == 0 { Ok(x / 3) } else { Err("Not divisible by 3") } } fn main() { let result = divide_by_two(12) .and_then(divide_by_three); // Chain the second operation match result { Ok(value) => println!("Success: {}", value), Err(error) => println!("Error: {}", error), } }
Transform Result
Values
The map
and map_err
methods let you transform the contents of Ok
and Err
respectively.
fn main() { let result: Result<i32, &str> = Ok(10); let doubled = result.map(|value| value * 2); // Applies a function to the `Ok` value println!("Doubled: {:?}", doubled); let error_mapped = result.map_err(|err| format!("Error: {}", err)); // Maps the `Err` value println!("Mapped error: {:?}", error_mapped); } #[test] fn test() { main(); }
Propagate Recoverable Errors with the ?
Operator
Use the ?
operator to propagate errors from a function call 'up the stack'.
If the value of the Result
is an Ok
, the value inside the Ok
will get returned, and the program will continue. If the value is an Err
, the Err
will be returned from the whole function, as if we had used the return
keyword, so the error value gets propagated to the calling code.
Note that we're only allowed to use the ?
operator in a function that returns Result
, Option
, or another type that implements std::ops::FromResidual
⮳.
//! Demonstrates the use of `?`. use std::fs::File; use std::io; use std::io::Read; /// Reads the username from the file "temp/hello.txt". /// /// # Returns /// /// Returns `Ok(String)` containing the username if successful, or /// `Err(io::Error)` if an error occurs. fn read_username_from_file() -> Result<String, io::Error> { // Note the `?`. If `open` fails, `read_username_from_file` returns its // error, short-circuiting the rest of the function. This function does // not panic on I/O failure. let mut username_file = File::open("temp/hello.txt")?; let mut username = String::new(); // Multiple `?` can be used within a function, but see the example below if // the error types are not the same. username_file.read_to_string(&mut username)?; // Note the final `Ok`. This is required by the return type of the function. Ok(username) } /// The main function demonstrates how to use `read_username_from_file` and /// handle its result. /// /// It either prints the username if the file is read successfully, or prints an /// error message if an error occurs. fn main() { match read_username_from_file() { Ok(name) => println!("User name: {}", name), Err(err) => println!("Error: {}", err), } }
Note that we could have used the common type alias type Result<T> = std::result::Result<T, std::io::Error>;
as the return type of read_username_from_file
.
The following example highlights the need to return Result<..., Box<dyn Error>>
(or a similar type) when multiple ?
operators are used in a given method and their error types are not the same:
use std::error::Error; /// Parses a string slice into a `u16` representing a port number. /// /// # Errors /// /// This function will return an error if the string cannot be parsed as a `u16` /// or if the parsed value is zero. fn parse_port(s: &str) -> Result<u16, Box<dyn Error>> { // We need to use `Box<dyn Error>` as the error type, because the returned // error type cannot be determined during compile time: It will either // contain an instance of `std::num::ParseIntError` (from the parse // method, when parsing fails), or a string (when the port is zero). // `Box` encapsulates the `dyn Error` trait object, because it is // dynamically sized. The trait object enables "late binding" a.k.a. // virtual dispatch at runtime, depending on the actual Error type. // Alternatively, you may return `anyhow::Result` - the `anyhow` crate // handles the complexity for you. let port: u16 = s.parse()?; if port == 0 { Err(Box::from(format!("Invalid port: {}", port))) } else { Ok(port) } } fn main() { match parse_port("123") { Ok(port) => println!("Port: {}", port), Err(err) => panic!("{}", err), } // Test with an invalid port number (zero). // match parse_port("0") { // Ok(port) => println!("Port: {}", port), // Err(err) => panic!("{}", err), // } }
Handle Errors in main
To handle a Result
in the main
function, you may:
- use a
match
,if let
, orwhile let
expression- to display or log the error, as described above,
- to attempt to recover from the error (for example by retrying the last operation).
- ignore the
Result
by assigning it tolet _ = ...
(rarely the right solution), - return a
Result
from themain
function.
The below example will tell how long the system has been running by opening the Unix file /proc/uptime
and parse the content to get the first number. It returns the uptime, unless there is an error.
//! This example demonstrates basic error handling in Rust using the `Result` //! type. use std::io; use std::io::BufRead; fn main() { // A `Cursor` behaves like a `File` but wraps an in-memory buffer. let mut my_cursor = io::Cursor::new(b"foo\nbar"); let mut my_string = String::new(); // `read_line` reads all bytes until a newline (the 0xA byte) is reached, // and append them to the provided String. `read_line` returns a // `Result` value, which is either success (`Ok`) or failure (`Err`). let result: Result<usize, std::io::Error> = my_cursor.read_line(&mut my_string); // We can process the `Result` as needed. match result { Ok(0) => println!("End of file reached."), Ok(n) => println!("The total number of bytes read is {n}."), Err(ref e) => eprintln!("Error: {}", e), /* We could also retry `read_line` to attempt to recover from the error. */ } assert_eq!(my_string, "foo\n"); }
#![allow(dead_code)] use std::fs::File; use std::io::Read; use anyhow::Result; use anyhow::anyhow; // Let's first define a function that returns a `Result`: /// Reads the system uptime. /// /// Returns the uptime in seconds as a `u64`. /// Returns an error if the file cannot be read or the data cannot be parsed. fn read_uptime() -> Result<u64> { // Open the file. // `?` will return early if the file cannot be opened. // Read the file content into a string. // `?` will return early if the file cannot be read. let mut uptime = String::new(); File::open("/proc/uptime")?.read_to_string(&mut uptime)?; Ok(uptime .split('.') .next() .ok_or(anyhow!("Cannot parse uptime data"))? // `ok_or` transforms an `Option` into a `Result`. .parse()?) } // The first method to handle // match read_uptime() { // Ok(uptime) => println!("uptime: {} seconds", uptime), // Err(err) => eprintln!("error: {}", err), // }; /// The main function of the program. /// /// This function is the entry point of the program. /// It does not do anything in this example. fn main() {}
Avoid Discarding Errors During Error Conversions
Uses reqwest::blocking
⮳ to query a random integer generator web service. Converts the string response into an integer.
//! This example demonstrates how to use the `anyhow` crate to handle errors //! in a Rust program. //! //! Add to your `Cargo.toml`: //! ```toml //! reqwest = { version = "0.12.12", features = ["blocking"] } //! ``` fn parse_response( response: reqwest::blocking::Response, ) -> anyhow::Result<u32> { let body = response.text()?; let body = body.trim(); let b = body.parse::<u32>()?; Ok(b) } // The `main` function may return a `Result` itself. // We return `anyhow::Result<()>` but you could use // `Result<(), Box<dyn Error>>` as the return type. fn main() -> anyhow::Result<()> { let url = "https://www.random.org/integers/?num=1&min=0&max=10&col=1&base=10&format=plain".to_string(); // Issue a HTTP GET request to the API above. let response = reqwest::blocking::get(url)?; // Parse the returned `Response` into an integer. let random_value: u32 = parse_response(response)?; println!("A random number between 0 and 10: {}", random_value); Ok(()) }
Obtain the Backtrace in Complex Error Scenarios
This recipe shows how to handle a complex error scenario and then print a backtrace.
The example attempts to deserialize the value 256
into a u8
⮳. An error will bubble up from serde
to csv
⮳ and finally up to the user code.
//! This example demonstrates how to use backtraces to debug errors. //! //! The example code defines a struct `Rgb` that represents a color in RGB //! format. use std::fmt; use anyhow::Result; use anyhow::anyhow; use serde::Deserialize; // use anyhow::Context; #[derive(Debug, Deserialize)] struct Rgb { red: u8, blue: u8, green: u8, } impl Rgb { /// The `Rgb::from_csv` function parses a CSV string and returns an `Rgb` /// struct. fn from_csv(csv_data: &[u8]) -> Result<Rgb> { let color = csv::Reader::from_reader(csv_data) // Returns a borrowed iterator over deserialized records. // Each item is a `Result<Rgb, Error>`. .deserialize() .nth(0) // Convert `None` into `Result::Err`. .ok_or(anyhow!("First CSV row does not exist."))?; Ok(color?) } } // The `UpperHex` trait is used to format the `Rgb` struct as a hexadecimal // string. impl fmt::UpperHex for Rgb { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let hexa = (u32::from(self.red) << 16) | (u32::from(self.blue) << 8) | u32::from(self.green); write!(f, "{:X}", hexa) } } /// The `main` function parses a CSV string and prints the result. fn main() -> Result<()> { let csv = "red,blue,green 102,256,204"; // Note the invalid value. let rgb = Rgb::from_csv(csv.as_bytes())?; println!("{:?} to hexadecimal #{:X}", rgb, rgb); Ok(()) }
Backtrace error rendered:
Error level - description
└> 0 - Cannot read CSV data
└> 1 - Cannot deserialize RGB color
└> 2 - CSV deserialize error: record 1 (line: 2, byte: 15): field 1: number too large to fit in target type
└> 3 - field 1: number too large to fit in target type
Run the recipe with RUST_BACKTRACE=1
to display a detailed backtrace associated with this error.