04open 25 min

Option, Result, and errors: explicit absence and explicit failure

Translate Python's None and exception habits into Rust's explicit sum types and propagation patterns.

by the end of this lesson you can

  • Chooses Option when absence is normal
  • Avoids inventing an error when there is only no match
  • Keeps caller handling explicit

Overview

Python often uses `None` for absence and exceptions for failure. Rust splits those concepts into `Option` and `Result`, which makes the shape of success, absence, and failure visible in the type itself.

In Python, you often

use `None` as a flexible placeholder and exceptions to signal failure, sometimes without a hard distinction between the two.

In Rust, the common pattern is

to use `Option<T>` when a value may be absent and `Result<T, E>` when an operation can fail.

why this difference matters

Separating absence from failure makes APIs easier to reason about and prevents callers from guessing what a missing value really means.

Python

user = lookup_user(id)
if user is None:
    raise ValueError("missing user")

Rust

let user = lookup_user(id).ok_or("missing user")?;

Deeper comparison

Python version

def load_profile(user_id):
    profile = lookup_profile(user_id)
    if profile is None:
        raise LookupError("missing profile")
    return profile

Rust version

fn load_profile(user_id: UserId) -> Result<Profile, ProfileError> {
    let profile = lookup_profile(user_id).ok_or(ProfileError::Missing)?;
    Ok(profile)
}

Reflect

Why is it useful to force the caller to distinguish 'no value' from 'operation failed'?

what a strong answer notices

A strong answer mentions clearer API contracts, fewer ambiguous call sites, and more deliberate error handling.

Rewrite

Rewrite this Python function so the Rust version uses Option or Result intentionally rather than a generic null-or-error mix.

Rewrite this Python

def first_admin(users):
    for user in users:
        if user.is_admin:
            return user
    return None

what good looks like

  • Chooses Option when absence is normal
  • Avoids inventing an error when there is only no match
  • Keeps caller handling explicit

Practice

Design a Rust function that reads configuration from disk, validates it, and returns a typed result that distinguishes parse failure from a missing field.

success criteria

  • Uses Result for actual failure
  • Explains where Option still appears inside the parsing flow
  • Shows how the caller would handle both cases clearly

Common mistakes

  • Using Result everywhere, even when Option is the clearer contract.
  • Treating Option like an inconvenience rather than a precise signal of absence.
  • Flattening all failure modes into strings too early.

takeaways

  • Separating absence from failure makes APIs easier to reason about and prevents callers from guessing what a missing value really means.
  • A strong answer mentions clearer API contracts, fewer ambiguous call sites, and more deliberate error handling.
  • Uses Result for actual failure