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.
Once your functions can fail in several different ways, a plain String error stops being enough. The Custom Errors page shows how to define your own error type, and anyhow and thiserror shows the crates most Rust projects use to cut down the boilerplate.
Practice
Section titled “Practice”1. Check an age
Section titled “1. Check an age”Write fn parse_age(input: &str) -> Result<u32, String>. It should return Err with a helpful message if the text isn’t a number, or if the number is over 150. Test it with "34", "abc" and "200". Hint: match on input.parse::<u32>(), and inside the Ok arm use an if to check the size.
Show solution
fn parse_age(input: &str) -> Result<u32, String> { match input.parse::<u32>() { Ok(age) => { if age > 150 { Err(format!("{age} is too old to be real")) } else { Ok(age) } } Err(_) => Err(format!("'{input}' is not a number")), }}
fn main() { for input in ["34", "abc", "200"] { match parse_age(input) { Ok(age) => println!("Age: {age}"), Err(e) => println!("Error: {e}"), } }}Output:
Age: 34Error: 'abc' is not a numberError: 200 is too old to be real2. Add two numbers with ?
Section titled “2. Add two numbers with ?”Write fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> that parses both strings and returns their sum. Use ? instead of match. Print the result for ("10", "20") and ("10", "twenty"). Hint: you need one ? per parse, and the last line wraps the sum in Ok(...).
Show solution
fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> { let x = a.parse::<i32>()?; let y = b.parse::<i32>()?; Ok(x + y)}
fn main() { println!("{:?}", add_strings("10", "20")); println!("{:?}", add_strings("10", "twenty"));}Output:
Ok(30)Err(ParseIntError { kind: InvalidDigit })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.
Quick check
Section titled “Quick check”Quick check
1. What are the two variants of Result<T, E>?
2. What does the ? operator do inside a function that returns a Result?
3. According to this page, when should you prefer Result over panic!?
4. In the divide example on this page, what does divide(10.0, 0.0) return?
5. Why does the ? operator only work inside a function that itself returns a Result (or Option)?
Score: 0 / 5
Read more
Section titled “Read more”- Recoverable Errors with Result — The Rust Book: the official chapter on
Result,unwrap,expectand the?operator, with file-opening examples. std::result— standard library docs: an overview of working withResult, including methods likemap_err,unwrap_orandok.- Result — Rust by Example: runnable examples of handling
Resultand returning it frommain. - The ? operator — Rust by Example: a short, focused look at how
?replaces nestedmatchblocks.