#11 Option and Result
Option<T>, Result<T,E>, ?
The Option type
Rust has no null value. Instead, it uses the Option<T> enum to represent a value that might or might not exist. This forces you to handle both cases explicitly, making your code safer.
enum Option<T> {
Some(T), // A value is present
None, // No value
}When a function might not return a value, it returns Option<T> instead of a raw value:
fn find_number(numbers: &[i32], target: i32) -> Option<i32> {
for &n in numbers {
if n == target {
return Some(n);
}
}
None
}Working with Option
There are several ways to extract the value from an Option. The simplest is pattern matching, but Rust provides many convenient methods.
let maybe = Some(42);
match maybe {
Some(value) => println!("Found: {}", value),
None => println!("Nothing here!"),
}unwrap() extracts the value but panics if it is None. Use it only when you are certain a value exists. unwrap_or() provides a default instead:
let x: Option<i32> = Some(10);
let y: Option<i32> = None;
println!("{}", x.unwrap()); // 10
println!("{}", y.unwrap_or(0)); // 0map() transforms the inner value without unwrapping. and_then() chains operations that themselves return an Option:
let name: Option<&str> = Some("Rust");
// map: transform the inner value
let upper = name.map(|n| n.to_uppercase());
println!("{:?}", upper); // Some("RUST")
// and_then: chain Option-returning operations
let first_char = name.and_then(|n| n.chars().next());
println!("{:?}", first_char); // Some('R')The Result type
Result<T, E> is used for operations that can fail. It carries either a success value (Ok) or an error (Err):
enum Result<T, E> {
Ok(T), // Success
Err(E), // Error
}fn parse_number(input: &str) -> Result<i32, String> {
match input.parse::<i32>() {
Ok(n) => Ok(n),
Err(e) => Err(format!("Failed to parse: {}", e)),
}
}
fn main() {
match parse_number("123") {
Ok(n) => println!("Parsed successfully: {}", n),
Err(e) => println!("Error: {}", e),
}
}The ? operator
The ? operator is syntactic sugar for propagating errors. If a Result is Err, it returns the error from the current function immediately. If it is Ok, it unwraps the value:
use std::fs;
use std::io;
fn read_config() -> Result<String, io::Error> {
let content = fs::read_to_string("config.toml")?;
Ok(content)
}
fn main() {
match read_config() {
Ok(config) => println!("Config: {}", config),
Err(e) => println!("Could not read config: {}", e),
}
}The ? operator also works with Option: if the value is None, the function returns None immediately.
Converting between Option and Result
You can convert an Option to a Result using ok_or() or ok_or_else(), and a Result to an Option using ok():
// Option -> Result
let maybe: Option<i32> = Some(42);
let result: Result<i32, &str> = maybe.ok_or("no value");
println!("{:?}", result); // Ok(42)
let empty: Option<i32> = None;
let result: Result<i32, &str> = empty.ok_or("no value");
println!("{:?}", result); // Err("no value")
// Result -> Option
let ok_val: Result<i32, &str> = Ok(10);
let option = ok_val.ok();
println!("{:?}", option); // Some(10)Summary
Option<T> // Value that might not exist
Some(value) // Has a value
None // No value
Result<T, E> // Operation that can fail
Ok(value) // Success
Err(error) // Failure
.unwrap() // Extract or panic
.unwrap_or(default) // Extract or use default
.map(|v| ...) // Transform inner value
.and_then(|v| ...) // Chain operations
.ok_or(err) // Option -> Result
.ok() // Result -> Option
? // Propagate error/NoneYour turn
Try running the Option and Result demo with cargo run and check for compilation errors with cargo check: