Digest: option and result
This digest introduces two of the most important enums in Rust: Option
and Result. They are the foundation of safe error handling and absence
handling. Understanding how to use them is essential for writing robust
Rust programs.
1. The option type
Option<T> represents a value that may or may not be present.
#![allow(unused)] fn main() { let maybe_num: Option<i32> = Some(42); let nothing: Option<i32> = None; }
Extracting values
- unwrap: take the value, panic if
None.
#![allow(unused)] fn main() { println!("{}", maybe_num.unwrap()); // 42 println!("{}", nothing.unwrap()); // panics }
- expect: like
unwrapbut with a custom panic message.
#![allow(unused)] fn main() { println!("{}", maybe_num.expect("Should have a value")); }
- if let: execute code only when the option is
Some.
#![allow(unused)] fn main() { if let Some(x) = maybe_num { println!("Got {}", x); } }
- match: handle both cases explicitly.
#![allow(unused)] fn main() { match maybe_num { Some(x) => println!("Got {}", x), None => println!("Got nothing"), } }
- unwrap_or / unwrap_or_else: provide a fallback value.
#![allow(unused)] fn main() { let x = nothing.unwrap_or(0); // x = 0 }
2. The result type
Result<T, E> represents success (Ok(T)) or error (Err(E)).
#![allow(unused)] fn main() { fn divide(a: i32, b: i32) -> Result<i32, &'static str> { if b == 0 { Err("cannot divide by zero") } else { Ok(a / b) } } }
Extracting values
- unwrap / expect: panic if
Err.
#![allow(unused)] fn main() { println!("{}", divide(4, 2).unwrap()); // 2 println!("{}", divide(4, 0).unwrap()); // panics }
- if let: handle only the success case.
#![allow(unused)] fn main() { if let Ok(val) = divide(4, 2) { println!("Result is {}", val); } }
- match: handle both success and error.
#![allow(unused)] fn main() { match divide(4, 0) { Ok(val) => println!("Result is {}", val), Err(e) => println!("Error: {}", e), } }
- unwrap_or / unwrap_or_else: provide a default on error.
#![allow(unused)] fn main() { let val = divide(4, 0).unwrap_or(-1); // val = -1 }
3. The question mark operator
The ? operator is the idiomatic way to propagate errors or None
values. It works only in functions returning Option or Result.
#![allow(unused)] fn main() { fn read_number() -> Result<i32, std::num::ParseIntError> { let text = "42"; let num: i32 = text.parse()?; // if parsing fails, return the error immediately Ok(num) } }
- With
Option,Nonewill be returned early. - With
Result,Err(e)will be returned early.
4. Rules of thumb
- Use
unwraporexpectonly in tests, quick prototypes, or when failure is impossible. - Use
if letwhen you only care about the success case. - Use
matchwhen you want to handle both success and failure explicitly. - Use
unwrap_orwhen a fallback value makes sense. - Use
?to simplify error propagation in functions that returnOptionorResult.
5. Putting it together
fn safe_divide(a: i32, b: i32) -> Option<i32> { if b == 0 { None } else { Some(a / b) } } fn main() -> Result<(), Box<dyn std::error::Error>> { // Option with match match safe_divide(4, 2) { Some(val) => println!("4/2 = {}", val), None => println!("division by zero"), } // Option with if let if let Some(val) = safe_divide(10, 5) { println!("10/5 = {}", val); } // Result with ? let number: i32 = "123".parse()?; // parse returns Result println!("Parsed: {}", number); Ok(()) }
This example illustrates how Option and Result can be used together
in practice.