Skip to content

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.

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
}

.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 })
▶ Try it Yourself
fn main() {
let input = "42";
match input.parse::<i32>() {
Ok(number) => println!("Parsed: {number}"),
Err(e) => println!("Failed to parse: {e}"),
}
}
▶ Try it Yourself

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: 5
Error: cannot divide by zero
▶ Try it Yourself

Returning 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.

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 })
▶ Try it Yourself

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.

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.
  • match handles both cases explicitly, same pattern as Option.
  • The ? operator returns an Err early automatically, so you don’t need a match after every fallible call.
  • Prefer Result over panic! for errors a caller could reasonably recover from.

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