❓

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