Option and Result
Rust has no null. A value that might be absent is wrapped in Option<T>, which is either Some(value) or None — and the compiler forces you to handle both cases before you can use the value inside, so a missing value can never be accidentally used as if it were present.
fn find_first_even(numbers: &[i32]) -> Option<i32> {
for &n in numbers {
if n % 2 == 0 {
return Some(n);
}
}
None
}
fn main() {
let numbers = vec![1, 3, 5, 4, 7];
match find_first_even(&numbers) {
Some(n) => println!("Found: {}", n),
None => println!("No even number found"),
}
}Found: 4
Result for recoverable errors
Result<T, E> represents an operation that might fail: Ok(value) on success, Err(error) on failure. There are no exceptions to throw or catch here — a function that can fail simply returns a Result, and match forces the caller to handle both outcomes explicitly.
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),
}
}Result: 5 Error: cannot divide by zero
Next: functions — parameters, return types, and Rust's expression-based syntax.
Practice exercise
Write a function first_char(s: &str) -> Option<char> that returns the first character of s, or None if s is empty. Call it with "rust" and match the result, printing the character if Some, or "empty" if None.