Result
Recipe | Crates |
---|---|
Handle Recoverable Errors with Result | |
Retrieve or Transform Values in Result | |
Get Reference to Values Inside a Result | |
Result vs Option |
Handle Recoverable Errors with Result
Result<T, E>
⮳ is an enum used for error handling. It has two variants:
Ok(T)
represents success and contains a value of typeT
.Err(E)
represents an error and contains an error value of typeE
.
Functions should return Result
when errors are expected and recoverable. Note that Result
is annotated with the #[must_use]
attribute: the compiler issues a warning when a Result
value is ignored.
The following example show to return Result
from a function, propagate it to the caller with the ?
operator, or process it with match
. The anyhow
and thiserror
libraries can be used to significantly simplify error handling.
//! `Result` is a type that represents either success (`Ok`) or failure (`Err`): //! //! ``` //! pub enum Result<T, E> { //! Ok(T), //! Err(E), //! } //! ``` use std::fs::File; use std::io; use std::io::Read; use std::num::ParseIntError; fn open_file(file_path: &str) { // Faillible functions like `File::open` return a `Result`... let open_result: Result<File, io::Error> = File::open(file_path); // You could handle their `Result` there and then... match open_result { Err(e) => eprintln!("An error occurred while opening the file: {}", e), Ok(file) => println!("Opened {:?}", file), } } // ...but most often you'll want to return early when encountering an error, // and propagate the error up the call stack. #[allow(clippy::question_mark)] fn read_file(file_path: &str) -> Result<String, io::Error> { let mut file = match File::open(file_path) { Ok(file) => file, Err(e) => return Err(e), }; // This said, having to use multiple `match` or `if let` is verbose // when chaining calls to faillible methods... let mut contents = String::new(); if let Err(e) = file.read_to_string(&mut contents) { Err(e) } else { Ok(contents) } } // Therefore, use the `?` operator as a shortcut to return early // in case of an error. The following is equivalent to the previous function. fn read_file2(file_path: &str) -> Result<String, io::Error> { let mut file: File = File::open(file_path)?; // Note that `file` is of type `File`, // not `io::Result<File> = Result<File, io::Error>` let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) } // You can even chain method calls immediately after the `?`, e.g. // `File::open(file_path)?.read_to_string(&mut contents)?;` // You will often need to return one of multiple `Result` types. // You could create a custom error `enum` to do so: fn read_and_parse_file(file_path: &str) -> Result<i32, MyError> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; // `read_to_string` returns `Result<_, io::Error>`. let number = contents.trim().parse()?; // `parse` returns `Result<_, std::num::ParseIntError>`. Ok(number) } #[allow(dead_code)] #[derive(Debug)] enum MyError { Io(io::Error), Parse(ParseIntError), } // That custom error type must implement the `From` trait. impl From<io::Error> for MyError { fn from(err: io::Error) -> MyError { MyError::Io(err) } } impl From<ParseIntError> for MyError { fn from(err: ParseIntError) -> MyError { MyError::Parse(err) } } // The `thisError` crate provides a convenient `derive` macro // for the standard library's `std::error::Error` trait. // Use when writing libraries, avoiding the need for custom error boilerplate. #[allow(dead_code)] #[derive(thiserror::Error, Debug)] enum MyError2 { #[error("Io error")] Io(#[from] io::Error), #[error("Parse error")] Parse(#[from] ParseIntError), } // A simpler method than returning a custom error type // may be to return a trait object (at the cost of opacity)... fn read_and_parse_file2( file_path: &str, ) -> Result<i32, Box<dyn std::error::Error>> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let number = contents.trim().parse()?; Ok(number) } // ...but crates like `anyhow` can simplify error management a great deal... // `anyhow::Result<T>` is equivalent to `std::result::Result<T, anyhow::Error>`. fn read_and_parse_file3(file_path: &str) -> anyhow::Result<i32> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let number = contents.trim().parse()?; Ok(number) } // A function without return value in truth returns `Unit`: // `fn func() {...}` is a shorthand for `fn func() -> () { ... }` // To convert it to a faillible function, return `Result<(), SomeErrorType>`. fn unit_return_value(file_path: &str) -> anyhow::Result<()> { let _i: i32 = read_and_parse_file3(file_path)?; println!("I don't return anything!"); // Do not forget to return an Result in the happy path. // The double parentheses are required. It is `Ok( () )`. Ok(()) } // `main()` can return a `Result`, more precisely a `Result<T, E> where T: // Termination, E: Debug)`. `Result<(), Box<dyn std::error::Error>>`, // `anyhow::Result<()>` are common choices. fn main() -> Result<(), Box<dyn std::error::Error>> { let file_path = "example.txt"; open_file(file_path); // Here we ignore the `Result` return values // to avoid returning early and exercise all the code. // You should rarely need to do this. let _ = read_file(file_path); let _ = read_file2(file_path); let _ = read_and_parse_file(file_path); let _ = read_and_parse_file2(file_path); let _ = read_and_parse_file3(file_path); let _ = unit_return_value(file_path); Ok(()) }
Retrieve or Transform Values in Result
In addition to working with pattern matching, Result
provides a wide variety of convenience methods, the most common being:
Method | Description | Use Case |
---|---|---|
unwrap() | Returns the Ok value or panics if Err . | Use when you're certain the result is Ok . See also expect . |
unwrap_or(default) | Returns the Ok value or a default if Err . | Use when you want a fallback value. |
unwrap_or_else(func) | Returns the Ok value or calls a function to generate a fallback. | Use when computing a fallback dynamically. |
map(func) | Applies a function to the Ok value. | Use to transform the result, if successful. |
map_err(func) | Applies a function to the Err value. | Use to transform the error. |
and_then(func) | Applies a function that returns Result , flattening the result. | Use for chaining operations that may fail. |
or_else(func) | Calls a function to provide an alternative Result , if Err . | Use to recover from errors dynamically. |
map_or(default, func) | Applies a function to the Ok value or returns a default if Err . | Use when you need a fallback value. |
map_or_else(err_func, ok_func) | Applies a function to the Ok value or calls another function for Err . | Use when you need dynamic error handling. |
is_ok() | Returns true if the result is Ok . | Use to check success. |
is_err() | Returns true if the result is Err . | Use to check failure. |
The following example demonstrates a few of these methods:
//! Example of `Result` methods for value transformation and control flow. // A contrived example of a function returning a `Result`: fn divide(a: i32, b: i32) -> Result<i32, &'static str> { if b == 0 { Err("Cannot divide by zero") } else { Ok(a / b) } } #[allow(clippy::bind_instead_of_map)] fn main() { let result = divide(10, 2) // Transform the inner value if `Ok`. .map(|x| x * 2) // Chain another `Result`-returning operation. .and_then(|x| Ok(x + 1)); // Check result status. println!("Is result Ok? {}", result.is_ok()); println!("Is result Err? {}", result.is_err()); println!("Final value: {}", result.unwrap_or(-1)); // Provide a default value if `Err`. // You could also write: .or_else(|_| Ok(-1)); // TODO Ok("").expect("failed to write message"); // Panic with a provided // custom message if `Err`. }
Get Reference to Values Inside a Result
The following example demonstrates the usage of the as_ref()
, as_deref()
, and as_mut()
methods on the Result
type:
as_ref()
converts&Result<T, E>
intoResult<&T, &E>
, allowing access to references.as_deref()
converts&Result<T, E>
intoResult<&U, &E> where T: Deref<Target = U>
, useful for convertingResult<String, E>
toResult<&str, E>
.as_mut()
converts&mut Result<T, E>
intoResult<&mut T, &mut E>
, allowing mutation of the inner value.
These methods are useful for working with references to the values inside a Result
without consuming the Result
itself.
//! Demonstrates the use of `as_ref`, `as_deref`, and `as_mut` methods on //! `Result`. fn get_message(success: bool) -> Result<String, &'static str> { if success { Ok(String::from("Rust")) } else { Err("Failed to get message") } } fn main() { let result: Result<String, &'static str> = get_message(true); // `as_ref` converts from `&Result<T, E>` to Result<&T, &E>`. let ref_result: Result<&String, &&str> = result.as_ref(); println!("as_ref(): {:?}", ref_result); // Use `as_deref` to coerce the `Ok` variant of the original `Result` via // `Deref`. Here, `&String` is coerced to `&str`. let deref_result: Result<&str, &&str> = result.as_deref(); println!("as_deref(): {:?}", deref_result); // Use `as_mut` to get a mutable reference: let mut result_mut = get_message(true); let mut_ref: Result<&mut String, &mut &str> = result_mut.as_mut(); if let Ok(s) = mut_ref { s.push_str(" is awesome!"); } println!("as_mut(): {:?}", result_mut); // There is also a `as_deref_mut` method. }
Result
vs Option
Option<T>
represents an optional value. Use Option
when a value might be missing but isn't necessarily an error (e.g., searching for an item in a list).
Result<T, E>
represents the outcome of an operation that can succeed or fail. Use Result
when an operation can fail and you need to handle errors explicitly (e.g., file I/O operations).
The following table lists common methods that convert Result<T, E>
to Option
:
| Method | Description | Use Case |
| ok()
| Converts Result<T, E>
into Option<T>
, discarding the error. | Use when you only care about the success value. |
| err()
| Converts Result<T, E>
into Option<E>
, discarding the success value. | Use when you only care about the error. |
See also the Option chapter.
Related Topics
- Error Handling.
- Error Customization.
- Iterators.
- Rust Patterns.