Bitterless Rust

Option and Result

Option: Something or Nothing

Rust has no null. Instead, it uses Option:

enum Option<T> {
    Some(T),  // there's a value
    None,     // there's no value
}

<T> means "any type." Option<i32> means "maybe an i32, maybe nothing." Option<String> means "maybe a String, maybe nothing."

fn find_even(numbers: Vec<i32>) -> Option<i32> {
    for n in numbers {
        if n % 2 == 0 {
            return Some(n);
        }
    }
    None
}

fn main() {
    let nums = vec![1, 3, 4, 7];
    let result = find_even(nums);

    match result {
        Some(n) => println!("found: {}", n),
        None => println!("not found"),
    }
}

How to Handle Option

unwrap: "It's definitely there!"

fn main() {
    let x: Option<i32> = Some(42);
    let value = x.unwrap(); // 42
    println!("{}", value);
}

If you unwrap() a None, the program crashes (panics). Only use this when you're sure it's Some.

expect: unwrap with a Message

fn main() {
    let x: Option<i32> = Some(42);
    let value = x.expect("value should exist");
    println!("{}", value);
}

Same as unwrap, but you get to specify the crash message.

match: Handle It Safely

fn main() {
    let x: Option<i32> = Some(42);

    match x {
        Some(v) => println!("got: {}", v),
        None => println!("nothing"),
    }
}

if let: When You Only Care About One Case

fn main() {
    let x: Option<i32> = Some(42);

    if let Some(v) = x {
        println!("got: {}", v);
    }
}

Handy when you want to do nothing for None.

Result: Success or Failure

Result represents whether an operation succeeded or failed:

enum Result<T, E> {
    Ok(T),   // success (value is T)
    Err(E),  // failure (error is E)
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("division by zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10.0, 3.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),
    }
}

You Can unwrap Result Too

fn main() {
    let result = divide(10.0, 3.0);
    let value = result.unwrap(); // 3.333...
    println!("{}", value);
}

If you unwrap() an Err, it panics. Same rules as Option.

The ? Operator: Propagate Errors Upward

Using ? inside a function returns the Err to the caller automatically:

fn calc(a: f64, b: f64, c: f64) -> Result<f64, String> {
    let ab = divide(a, b)?;  // if Err, return immediately
    let result = divide(ab, c)?;
    Ok(result)
}

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("division by zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match calc(100.0, 5.0, 2.0) {
        Ok(v) => println!("result: {}", v),
        Err(e) => println!("error: {}", e),
    }
}

? means "if Ok, extract the value; if Err, return that Err from this function immediately." Can only be used in functions that return Result.

Common Patterns

Many standard library functions return Result:

fn main() {
    // String to number (might fail)
    let num: Result<i32, _> = "42".parse();
    println!("{}", num.unwrap()); // 42

    let bad: Result<i32, _> = "hello".parse();
    println!("{}", bad.is_err()); // true
}

_ means "let the compiler figure out the type."

Summary

Type Meaning Variants
Option<T> Something or nothing Some(T) / None
Result<T, E> Success or failure Ok(T) / Err(E)

How to handle them:

Approach When to use
match When you want to safely handle both cases
if let When one case is enough
unwrap() / expect() When you're sure it's Some/Ok
? When you want to propagate errors to the caller

Congrats. The Rust fundamentals are done. Starting next chapter, we'll use everything you've learned to build a four-operation calculator.

Back to book