So You're a Ruby/Python Dev Learning Rust's Option Type

Published: (January 9, 2026 at 01:00 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

What You’re Used To

Python

user = find_user(id)
email = user.email.upper()  # fingers crossed

If user is None you’ll get a NoneType error. The usual workaround:

email = user.email.upper() if user else None

Ruby

user = find_user(id)
email = user.email.upcase  # YOLO

Or, a bit safer:

email = user&.email&.upcase

Both languages let you forget to check, and that can bring a production service down.

Rust’s “Actually, Let’s Not Do That” Approach

Rust has no null. Instead you get an Option:

let user: Option<_> = find_user(id);
  • Some(user) – we found something
  • None – nothing there

The compiler won’t let you pretend it’s always Some. You must handle both cases, or the code won’t compile. After a short learning curve you’ll notice you’ve stopped debugging null‑pointer crashes forever.

The Combinators (Fancy Name, Simple Concept)

.map() – Do something if there’s a value

Python / Ruby

email = user.email.upper() if user else None

Rust

let email = user.map(|u| u.email.to_uppercase());

If user is Some, the closure runs; if it’s None, the result is None.

.unwrap_or() – Provide a fallback

Python

port = config.get('port') or 8080

Rust

let port = config.port.unwrap_or(8080);

Straightforward defaulting.

.and_then() – Chain operations that may return None

Python / Ruby

result = None
if user:
    profile = get_profile(user.id)
    if profile:
        result = profile.email

Rust

let result = user
    .and_then(|u| get_profile(u.id))
    .and_then(|p| p.email);

Each .and_then() runs only if the previous step produced Some; otherwise the whole chain yields None.

The ? Operator – Early return on None or Err

fn get_user_email(id: u32) -> Result<String, Error> {
    let user = find_user(id)?;                     // Propagate Err/None
    let email = user.email.ok_or(Error::NoEmail)?; // Propagate if None
    Ok(email.to_uppercase())
}

? says “if this is an error (or None for Option), return it immediately; otherwise unwrap and continue.” It’s like Ruby’s safe navigation (&.) but actually propagates the error.

Common Mistakes (I Made All of These)

❌ Don’t do this

let user = find_user(id).unwrap();   // Crashes on None

That’s the Rust equivalent of ignoring nil checks.

✅ Do this instead

// Let the caller handle the error
let user = find_user(id)?;

// Or provide a sensible default
let user = find_user(id).unwrap_or_default();

// Or match explicitly
match find_user(id) {
    Some(u) => handle_user(u),
    None    => handle_missing(),
}

If you truly believe the value must exist, use .expect("reason") so the panic message is helpful:

let config = load_config().expect("config.toml must exist");

Quick Reference

What you wantHow to do it
Transform the value`.map(
Chain Option‑returning calls`.and_then(
Use a default.unwrap_or(default)
Return early on None/Err?
Different logic per casematch … { Some(v) => …, None => … }
Crash with a message (tests only).expect("message")

FAQ

Q: Why is this better than just checking for nil?
A: You can’t forget. The compiler forces you to handle the case, so you never get a surprise null‑pointer crash in production.

Q: What if I know for sure it’s Some?
A: Use .expect("explanation"). If it ever panics, the message tells you why you thought it was safe.

Q: This looks more verbose than Python/Ruby.
A: You’re not writing more code; you’re making error handling explicit. In dynamic languages the handling is still there—it’s just hidden and easy to overlook. Once you’re used to it, you’ll appreciate the safety net.

Yourself: How many times have you shipped code that didn’t properly check for None/nil?

Q: Can I just unwrap everything and move on?

You can, but then why are you using Rust? That’s like buying a car with airbags and then disabling them. The whole point is catching this stuff before it becomes a production issue.

Real Talk

Coming from Ruby/Python, Rust’s Option handling feels like overkill at first. You’ll be annoyed. You’ll complain about the compiler. You’ll wonder why you can’t just check if something is None like a normal person.

Then one day you’ll realize you haven’t debugged a NoneType error in months. You’ll refactor some code and the compiler will catch all the places where you forgot to handle the new None case. You’ll ship with confidence because if it compiles, those error paths are actually handled.

That’s when it clicks.

The learning curve sucks, not gonna lie. But the compiler is basically doing the code review you’d normally catch in production. It’s annoying in the way a good senior dev is annoying when they point out all the edge cases you missed.

Anyway, hope this helps. Go write some Rust. Make mistakes. Let the compiler yell at you. You’ll get there.

Written by someone who absolutely tried to .unwrap() (Cloudflare :) ) everything for the first two weeks and learned the hard way.

Back to Blog

Related posts

Read more »