Option and Result
Option: Something or Nothing
Rust has no null. Instead, it uses Option:
enum Option<T> {
Some(T), // there's a value
None, // there's no value
}
<T>means "any type."Option<i32>means "maybe an i32, maybe nothing."Option<String>means "maybe a String, maybe nothing."
fn find_even(numbers: Vec<i32>) -> Option<i32> {
for n in numbers {
if n % 2 == 0 {
return Some(n);
}
}
None
}
fn main() {
let nums = vec![1, 3, 4, 7];
let result = find_even(nums);
match result {
Some(n) => println!("found: {}", n),
None => println!("not found"),
}
}
How to Handle Option
unwrap: "It's definitely there!"
fn main() {
let x: Option<i32> = Some(42);
let value = x.unwrap(); // 42
println!("{}", value);
}
If you unwrap() a None, the program crashes (panics). Only use this when you're sure it's Some.
expect: unwrap with a Message
fn main() {
let x: Option<i32> = Some(42);
let value = x.expect("value should exist");
println!("{}", value);
}
Same as unwrap, but you get to specify the crash message.
match: Handle It Safely
fn main() {
let x: Option<i32> = Some(42);
match x {
Some(v) => println!("got: {}", v),
None => println!("nothing"),
}
}
if let: When You Only Care About One Case
fn main() {
let x: Option<i32> = Some(42);
if let Some(v) = x {
println!("got: {}", v);
}
}
Handy when you want to do nothing for None.
Result: Success or Failure
Result represents whether an operation succeeded or failed:
enum Result<T, E> {
Ok(T), // success (value is T)
Err(E), // failure (error is E)
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("division by zero"))
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 3.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),
}
}
You Can unwrap Result Too
fn main() {
let result = divide(10.0, 3.0);
let value = result.unwrap(); // 3.333...
println!("{}", value);
}
If you unwrap() an Err, it panics. Same rules as Option.
The ? Operator: Propagate Errors Upward
Using ? inside a function returns the Err to the caller automatically:
fn calc(a: f64, b: f64, c: f64) -> Result<f64, String> {
let ab = divide(a, b)?; // if Err, return immediately
let result = divide(ab, c)?;
Ok(result)
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("division by zero"))
} else {
Ok(a / b)
}
}
fn main() {
match calc(100.0, 5.0, 2.0) {
Ok(v) => println!("result: {}", v),
Err(e) => println!("error: {}", e),
}
}
? means "if Ok, extract the value; if Err, return that Err from this function immediately." Can only be used in functions that return Result.
Common Patterns
Many standard library functions return Result:
fn main() {
// String to number (might fail)
let num: Result<i32, _> = "42".parse();
println!("{}", num.unwrap()); // 42
let bad: Result<i32, _> = "hello".parse();
println!("{}", bad.is_err()); // true
}
_means "let the compiler figure out the type."
Summary
| Type | Meaning | Variants |
|---|---|---|
Option<T> |
Something or nothing | Some(T) / None |
Result<T, E> |
Success or failure | Ok(T) / Err(E) |
How to handle them:
| Approach | When to use |
|---|---|
match |
When you want to safely handle both cases |
if let |
When one case is enough |
unwrap() / expect() |
When you're sure it's Some/Ok |
? |
When you want to propagate errors to the caller |
Congrats. The Rust fundamentals are done. Starting next chapter, we'll use everything you've learned to build a four-operation calculator.