About

Option<T> — Surviving in a World Without nil

How Ruby's nil and &. operator translate to Rust

In Ruby, nil is everywhere. Access a missing array index — nil. Missing hash key — nil. Method returns nothing explicitly — nil. Call a method on nil — NoMethodError. A huge portion of production errors come from here.

Rust eliminated nil entirely. Instead, it uses the Option<T> enum.

enum Option<T> {
    Some(T),  // value exists
    None,     // no value
}

Compared to Ruby

# Ruby
def find_user(id)
  users[id]  # might be nil
end
user = find_user(99)
user.name  # NoMethodError if nil!

// Rust
fn find_user(id: u32) -> Option<User> {
    // Some(user) or None
}
let user = find_user(99);
// user.name  // compile error! Option<User> has no name method

Rust treats Option<User> and User as different types. You must extract the value from Option to use it.

Extracting — unwrap (dangerous)

let user = find_user(99).unwrap();  // panics if None!

Similar risk to calling methods on nil in Ruby. Avoid in production code.

Safe extraction — match, if let, map

// match
match find_user(99) {
    Some(user) => println!("{}", user.name),
    None => println!("user not found"),
}

// if let (check one case)
if let Some(user) = find_user(99) {
    println!("{}", user.name);
}

// map — closest to Ruby's &.
let name = find_user(99).map(|u| u.name);
// returns Option<String>

&. → map/and_then mapping

Ruby's safe navigation vs Option methods:

# Ruby
user&.name              # returns nil if nil
user&.address&.city     # chaining
user&.name || "unknown" # default

// Rust
user.map(|u| u.name)                          // None if None
user.and_then(|u| u.address).map(|a| a.city)  // chaining
user.map(|u| u.name).unwrap_or("unknown")     // default

map transforms the value inside Some. and_then is for when the transform returns another Option (flatMap). unwrap_or provides a default for None.

unwrap_or_else — when the default is expensive

let name = find_user(99)
    .map(|u| u.name)
    .unwrap_or_else(|| fetch_default_name()); // only runs on None

Ruby's || in user&.name || expensive_default doesn't evaluate the right side unless needed. Rust's unwrap_or_else has the same lazy evaluation.

Key Points

1

Option<T> is Some(value) or None — type-safe nil replacement

2

unwrap is dangerous — use match, if let, map for safe handling

3

&. → map, chaining → and_then, default → unwrap_or

4

Option<User> and User are different types — must extract to use

Pros

  • ✓ NoMethodError on nil is literally impossible — caught at compile time
  • ✓ Forgetting to handle None means code won't compile

Cons

  • ✗ Can't ignore nil like Ruby — feels cumbersome early on
  • ✗ Long Option chains can reduce readability

Use Cases

When DB query might return nothing — find vs find_by When config values may or may not exist
← Enums & Pattern Matching — A Powerful Evolution of Symbol and case/when Result & Error Handling — What Fills the begin/rescue Void →