Rust Result
Where Option represents a value that might be missing, Result represents an operation that might fail — and unlike a missing value, a failure usually comes with a reason attached.
What Result looks like
Section titled “What Result looks like”Result is another enum from the standard library, this time with two variants that both carry data:
enum Result<T, E> { Ok(T), // success, holding a value Err(E), // failure, holding an error}A function that can fail
Section titled “A function that can fail”.parse(), which you met on the Type Casting page, is a good real example — turning text into a number can fail if the text isn’t actually a number:
fn main() { let good: Result<i32, std::num::ParseIntError> = "42".parse(); let bad: Result<i32, std::num::ParseIntError> = "abc".parse();
println!("{:?}", good); println!("{:?}", bad);}Output:
Ok(42)Err(ParseIntError { kind: InvalidDigit })Handling a Result with match
Section titled “Handling a Result with match”fn main() { let input = "42";
match input.parse::<i32>() { Ok(number) => println!("Parsed: {number}"), Err(e) => println!("Failed to parse: {e}"), }}Same shape as handling an Option with match — except the Err arm gives you an actual error value to inspect or report, not just “nothing was there.”
Writing your own function that returns Result
Section titled “Writing your own function that returns Result”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() { match divide(10.0, 2.0) { Ok(result) => println!("Result: {result}"), Err(e) => println!("Error: {e}"), }
match divide(10.0, 0.0) { Ok(result) => println!("Result: {result}"), Err(e) => println!("Error: {e}"), }}Output:
Result: 5Error: cannot divide by zeroReturning Result<f64, String> forces every caller to deal with the possibility of failure — there’s no way to accidentally treat a division-by-zero as a normal number.
The ? operator
Section titled “The ? operator”Chaining several operations that can each fail leads to deeply nested match blocks. The ? operator flattens that: it means “if this is Err, return that error immediately from the current function; otherwise, give me the value inside Ok.”
fn parse_and_double(input: &str) -> Result<i32, std::num::ParseIntError> { let number = input.parse::<i32>()?; // returns early if parsing fails Ok(number * 2)}
fn main() { println!("{:?}", parse_and_double("21")); println!("{:?}", parse_and_double("oops"));}Output:
Ok(42)Err(ParseIntError { kind: InvalidDigit })Without ?, that line would need its own match just to unwrap number or bail out early with the error — ? does exactly that in one character. It only works inside a function that itself returns a Result (or Option), since that’s what gives it somewhere to return the error to.
Result vs panic!
Section titled “Result vs panic!”Rust also has panic!, which stops the program immediately — that’s covered in more depth on the next page. As a rule of thumb: use Result for failures a caller might reasonably want to handle (bad input, a missing file); reserve panic! for situations that indicate an actual bug, where continuing would be worse than stopping.
Result<T, E>represents an operation that can succeed (Ok) or fail (Err) with an error value.matchhandles both cases explicitly, same pattern asOption.- The
?operator returns anErrearly automatically, so you don’t need amatchafter every fallible call. - Prefer
Resultoverpanic!for errors a caller could reasonably recover from.