---
title: Result and Option
url: https://doc.liz6.com/en/rust/05-error-handling/01-Result-Option
locale: en
area: rust
tags:
- rust
- error-handling
date: 2026-06-30
modified: 2026-07-16
description: Option represents "a value might not exist", Result represents "an operation might fail"—both are enums, not exceptions. The `?` operator returns early when encountering None/Err, and combinator chains (map/and_then/or_else) turn error handling into a type-safe pipeline rather than control-flow jumps via try-catch.
---

# Result and Option

> Option represents "a value might not exist", Result represents "an operation might fail"—both are enums, not exceptions. The `?` operator returns early when encountering None/Err, and combinator chains (map/and_then/or_else) turn error handling into a type-safe pipeline rather than control-flow jumps via try-catch.

## Option: A Value Might Not Exist

```rust
fn find_user(id: u64) -> Option<User> { ... }

find_user(42)
    .map(|u| u.name)                 // Option<User> → Option<String>, transform
    .and_then(|name| db_lookup(&name)) // Option<String> → Option<Record>, chain
    .filter(|r| r.active)            // keep only if active
    .unwrap_or(default_record);      // extract or use default
```

Each combinator only handles the `Some` case—`None` short-circuits automatically. This pattern is known in functional programming as "railway oriented programming": either you stay on the success track, or you instantly jump to the failure track and stay there, checking for nulls nowhere along the way. The compiler forces you to provide a default at `unwrap_or`—you can't forget to handle missing values.

## Result: Errors Are Values, Not Exceptions

```rust
fn parse(s: &str) -> Result<i32, ParseIntError> { s.parse() }

let n = match parse("42") {
    Ok(n) => n * 2,
    Err(e) => { eprintln!("parse failed: {}", e); return; }
};
```

The core difference from exceptions: **errors are part of the return value**, so callers know from the function signature that it might fail. There is no hidden control flow, no need to remember "which exceptions this function throws." The compiler forces you to handle `Result`—the `#[must_use]` attribute generates a compiler warning for unused Results.

## ? Operator: Early Return on Errors

```rust
fn read_config(path: &str) -> Result<Config, Error> {
    let file = std::fs::read_to_string(path)?;    // Err → early return
    let config: Config = toml::from_str(&file)?;
    Ok(config)
}
```

Expanded equivalent:

```rust
let file = match std::fs::read_to_string(path) {
    Ok(v) => v,
    Err(e) => return Err(e.into()),  // .into() converts error types via the From trait
};
```

The `?` operator calls `From::from` to perform automatic error type conversion—as long as `From<io::Error> for MyError` is implemented, the `io::Error` from `std::fs::read_to_string()?` will automatically convert to `MyError`. This is key to Rust's error propagation—you don't need to manually wrap errors at every layer.

## main Returns Result

```rust
fn main() -> Result<(), Box<dyn Error>> {
    let config = read_config()?;     // ? in main!
    Ok(())
}
```

When `main` returns a `Result`, if `main` returns `Err`, the runtime prints the error and exits (exit code 1). This is suitable for simple CLI tools. Complex applications typically have their own error handling and logging.

## References

- **Rust Book**: Chapter 9
- **Rust By Example**: Option/Result, ?

*Keywords: Option, Result, combinator, and_then, ?, error propagation, From trait, #[must_use]*
