Phase 3Data structures

#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.

Option definition
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:

Returning an Option
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.

Pattern matching
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:

unwrap and unwrap_or
let x: Option<i32> = Some(10);
let y: Option<i32> = None;

println!("{}", x.unwrap());       // 10
println!("{}", y.unwrap_or(0));   // 0

map() transforms the inner value without unwrapping. and_then() chains operations that themselves return an Option:

map and and_then
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):

Result definition
enum Result<T, E> {
    Ok(T),   // Success
    Err(E),  // Error
}
Using Result
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:

The ? operator
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():

Conversions
// 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

Quick reference
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/None

Your turn

Try running the Option and Result demo with cargo run and check for compilation errors with cargo check:

terminal — cargo
user@stemlegacy:~/option-result-demo$