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.

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.

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: 34
Error: 'abc' is not a number
Error: 200 is too old to be real
▶ Try it Yourself

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