Rust doesn't have null or exceptions. Instead it uses two enums:
enum Option<T> {
Some(T),
None,
}
Use when a value might not exist:
fn find_user(id: u64) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
match find_user(1) {
Some(name) => println!("Found: {name}"),
None => println!("Not found"),
}
enum Result<T, E> {
Ok(T),
Err(E),
}
Use when an operation can fail:
use std::num::ParseIntError;
fn parse_age(input: &str) -> Result<u8, ParseIntError> {
input.parse::<u8>()
}
Propagate errors without verbose match blocks:
fn get_user_age(input: &str) -> Result<u8, ParseIntError> {
let age = input.parse::<u8>()?; // returns Err early if parse fails
Ok(age)
}
// Option
let name = find_user(1).unwrap_or(String::from("Unknown"));
let upper = find_user(1).map(|n| n.to_uppercase());
// Result
let age = parse_age("25").unwrap_or(0);
let doubled = parse_age("25").map(|a| a * 2);
Anchor programs return Result<()>. Every instruction handler uses ? to propagate errors. Understanding this pattern is essential.