Result<T, E> / Ok / Err
| Since: | Rust 1.0(2015) |
|---|
Result<T, E> is an enum that represents the outcome of an operation as either success (Ok) or failure (Err). Instead of exceptions, Rust uses Result to force you to handle potential errors at compile time.
Syntax
// Wrap a success value in Ok.
let ok: Result<i32, String> = Ok(42);
// Wrap an error value in Err.
let err: Result<i32, String> = Err(String::from("An error occurred."));
// Use match to branch on success or failure.
match result {
Ok(value) => println!("Success: {}", value),
Err(e) => println!("Failure: {}", e),
}
// Use if let to handle only the success case.
if let Ok(value) = result {
println!("Success: {}", value);
}
Syntax reference
| Syntax / Variant | Description |
|---|---|
| Ok(value) | A variant indicating that the operation succeeded. Holds the resulting success value. |
| Err(error) | A variant indicating that the operation failed. Holds details about the error. Any type can be used as the error type. |
| match | Branches on the two patterns Ok(v) and Err(e). Both arms are required, preventing you from accidentally ignoring errors. |
| if let Ok(v) | Syntactic sugar for handling only the success case. Overusing it can lead to missed errors, as it silently ignores the Err case. |
| is_ok() / is_err() | Returns a boolean indicating whether the result is a success or failure. Use these when you only need to check the state without processing the value. |
Sample code
sample_result_ok_err.rs
// A function that divides two numbers and returns Err on division by zero.
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Cannot divide by zero."))
} else {
Ok(a / b)
}
}
fn main() {
// Handle the success case with match.
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
// Handle the failure case with match.
match divide(10.0, 0.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
// Check the state with is_ok / is_err.
let r1 = divide(10.0, 2.0);
let r2 = divide(10.0, 0.0);
println!("r1 is_ok: {}", r1.is_ok()); // true
println!("r2 is_err: {}", r2.is_err()); // true
// Parsing from the standard library also returns a Result.
let parsed: Result<i32, _> = "42".parse();
println!("{:?}", parsed); // Ok(42)
let failed: Result<i32, _> = "abc".parse();
println!("{:?}", failed); // Err(...)
}
Compile with the following command:
rustc result_ok_err.rs
./result_ok_err
Result: 5
Error: Cannot divide by zero.
r1 is_ok: true
r2 is_err: true
Ok(42)
Err(ParseIntError { kind: InvalidDigit })
Common Mistakes
Mistake 1: Discarding a Result and getting a compiler warning
If you drop the return value of a function that returns Result without using it, the compiler will issue a warning. Because the error is silently ignored, this can cause problems in production.
result_mistake1.rs
use std::fs;
fn main() {
// Ignoring the Result produces a compiler warning.
fs::remove_file("nonexistent.txt");
println!("Done");
}
Compile with the following command:
rustc result_mistake1.rs warning: unused `Result` that must be used --> result_mistake1.rs:5:5
Either handle the Result explicitly, or use let _ = to signal that ignoring it is intentional.
result_fix1.rs
use std::fs;
fn main() {
// Handle with match.
match fs::remove_file("nonexistent.txt") {
Ok(_) => println!("Deleted"),
Err(e) => println!("Delete failed: {}", e),
}
// Use let _ = to explicitly signal intentional ignoring.
let _ = fs::remove_file("already_gone.txt");
}
Compile with the following command:
rustc result_fix1.rs ./result_fix1 Delete failed: No such file or directory (os error 2)
Mistake 2: Overusing unwrap() and causing a panic
unwrap() panics when the value is Err. Unless you are certain that an error cannot occur, handle the result properly using match, if let, or the ? operator.
result_mistake2.rs
fn main() {
// Calling unwrap() on user input is risky.
let input = "abc"; // not a number
let n: i32 = input.parse().unwrap(); // panic!
println!("{}", n);
}
Compile with the following command:
rustc result_mistake2.rs
./result_mistake2
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }'
Use unwrap_or(), unwrap_or_else(), or match to handle the error.
result_fix2.rs
fn parse_score(s: &str) -> i32 {
s.parse::<i32>().unwrap_or_else(|e| {
println!("Parse failed '{}': {} -> using 0", s, e);
0
})
}
fn main() {
println!("{}", parse_score("95")); // 95
println!("{}", parse_score("abc")); // parse failed -> 0
println!("{}", parse_score("-5")); // -5
}
Compile with the following command:
rustc result_fix2.rs ./result_fix2 95 Parse failed 'abc': invalid digit found in string -> using 0 0 -5
Notes
In the type parameter Result<T, E>, T is the type of the success value and E is the type of the error value. You can use any type for the error, such as the standard library's std::io::Error or a custom type. If you discard the return value of a function that returns Result without using it, the compiler will issue a warning. Errors must always be handled explicitly or explicitly ignored.
Almost every operation in Rust's standard library that can fail — file I/O, networking, parsing, and more — returns a Result. This is the Rust equivalent of try-catch in other languages. Because Rust has no exceptions, treating errors as explicit return values is the idiomatic Rust approach.
If you find any errors or copyright issues, please contact us.