Option

#122 May 2026

122. Option::filter — Keep Some Only When the Value Passes

You’ve got an Option<T>, but you only want to keep the Some if the value passes a test. The match-with-guard version works — Option::filter says the same thing in one call.

The shape that keeps showing up: parse something into an Option, then validate it. The naive version stacks an if on top of the unwrap:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn parse_port(raw: Option<&str>) -> Option<u16> {
    let s = raw?;
    let n: u16 = s.parse().ok()?;
    if n > 0 { Some(n) } else { None }
}

assert_eq!(parse_port(Some("8080")), Some(8080));
assert_eq!(parse_port(Some("0")),    None);
assert_eq!(parse_port(Some("nope")), None);
assert_eq!(parse_port(None),         None);

Option::filter collapses that trailing if into the chain:

1
2
3
4
5
6
7
fn parse_port(raw: Option<&str>) -> Option<u16> {
    raw.and_then(|s| s.parse().ok())
       .filter(|&n| n > 0)
}

assert_eq!(parse_port(Some("8080")), Some(8080));
assert_eq!(parse_port(Some("0")),    None);

Same story for the match-with-guard pattern — when the predicate is the only thing the arm checks, filter reads better:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let name: Option<&str> = Some("");

// Before: pattern match with a guard
let valid = match name {
    Some(s) if !s.is_empty() => Some(s),
    _ => None,
};
assert_eq!(valid, None);

// After: just say what you mean
let valid = name.filter(|s| !s.is_empty());
assert_eq!(valid, None);

Two things worth knowing. First, the closure receives &T, not T — same as Iterator::filter. So for Option<i32> you write |&n| n > 0 or |n| *n > 0. For Option<String> auto-deref makes |s| !s.is_empty() just work.

Second, filter only keeps or drops — it never transforms. If the predicate returns true you get the original Some back, untouched. To transform, chain .map() after:

1
2
3
4
let trimmed = Some("  hello  ")
    .map(str::trim)
    .filter(|s| !s.is_empty());
assert_eq!(trimmed, Some("hello"));

Stable since Rust 1.27, and the kind of method that quietly disappears the boilerplate once you know it’s there.

114. Option::transpose — Use ? on an Optional Result

Got an Option<Result<T, E>> and want to ? the error out? You can’t — ? doesn’t reach inside the Option. transpose flips it to Result<Option<T>, E>, and the rest takes care of itself.

The classic case: a config field that’s optional, but if it’s there, it has to parse. The old dance is three match arms just to thread the error out of the Option:

1
2
3
4
5
6
7
8
9
use std::num::ParseIntError;

fn parse_port_old(raw: Option<&str>) -> Result<Option<u16>, ParseIntError> {
    match raw.map(str::parse::<u16>) {
        Some(Ok(p))  => Ok(Some(p)),
        Some(Err(e)) => Err(e),
        None         => Ok(None),
    }
}

transpose collapses it:

1
2
3
4
5
use std::num::ParseIntError;

fn parse_port(raw: Option<&str>) -> Result<Option<u16>, ParseIntError> {
    raw.map(str::parse::<u16>).transpose()
}

Option<Result<T, E>>::transpose() returns Result<Option<T>, E> — exactly the shape ? wants. Now you can chain it inline:

1
2
3
4
5
6
7
use std::collections::HashMap;
use std::num::ParseIntError;

fn read_port(config: &HashMap<&str, &str>) -> Result<Option<u16>, ParseIntError> {
    let port = config.get("port").copied().map(str::parse::<u16>).transpose()?;
    Ok(port)
}

It works the other way too: Result<Option<T>, E>::transpose() returns Option<Result<T, E>>. Handy when an iterator chain wants the Option on the outside.

All three cases, one call:

1
2
3
assert_eq!(parse_port(Some("8080")).unwrap(), Some(8080));
assert_eq!(parse_port(None).unwrap(), None);
assert!(parse_port(Some("nope")).is_err());

Reach for transpose any time Option and Result get nested and you wish ? could see through both.

#103 Apr 2026

103. bool::then_some — Turn a Condition Into an Option Without an if

Writing if condition { Some(value) } else { None } for the hundredth time? bool::then_some collapses that whole pattern into a single chainable call.

The familiar boilerplate

You have a bool and you want to lift it into an Option. The naive form works, but it’s noisy and breaks chains:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn even_double(n: i32) -> Option<i32> {
    if n % 2 == 0 {
        Some(n * 2)
    } else {
        None
    }
}

assert_eq!(even_double(4), Some(8));
assert_eq!(even_double(5), None);

Three lines and an if/else for what is conceptually “if true, wrap it.” It also doesn’t slot into iterator chains — you’d have to wrap it in a closure.

then_some: the eager form

bool::then_some(value) returns Some(value) if the bool is true, None otherwise:

1
2
3
4
5
6
fn even_double(n: i32) -> Option<i32> {
    (n % 2 == 0).then_some(n * 2)
}

assert_eq!(even_double(4), Some(8));
assert_eq!(even_double(5), None);

One line, no branches in sight. The intent reads exactly like the English: if even, then some n * 2.

then: the lazy form

then_some always evaluates its argument — fine for cheap values, wasteful for expensive ones. Use then(|| ...) when the value should only be computed when the condition holds:

1
2
3
4
5
6
7
fn parse_if_digits(s: &str) -> Option<u64> {
    s.chars().all(|c| c.is_ascii_digit())
        .then(|| s.parse().unwrap())
}

assert_eq!(parse_if_digits("42"), Some(42));
assert_eq!(parse_if_digits("4a2"), None);

The parse only runs when the string is all digits. With then_some(s.parse().unwrap()) you’d unwrap on every call — instant panic on bad input.

Where it really shines: filter_map chains

The big payoff is in iterator pipelines. Filter and transform in one step, no closure-returning-Option boilerplate:

1
2
3
4
5
6
7
8
let nums = [1, 2, 3, 4, 5, 6];

let doubled_evens: Vec<i32> = nums
    .iter()
    .filter_map(|&n| (n % 2 == 0).then_some(n * 2))
    .collect();

assert_eq!(doubled_evens, [4, 8, 12]);

Compare to the if/else version inside the closure — it works, but it’s twice as wide and the else None arm adds zero information.

Watch the eager trap

then_some evaluates its argument unconditionally — which means an “obvious” guard like this is actually a panic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn first<T: Copy>(items: &[T]) -> Option<T> {
    // BAD: items[0] runs even when the slice is empty.
    // (!items.is_empty()).then_some(items[0])

    // GOOD: defer the indexing into the closure.
    (!items.is_empty()).then(|| items[0])
}

assert_eq!(first(&[10, 20, 30]), Some(10));
assert_eq!(first::<i32>(&[]), None);

The bool short-circuits, the closure doesn’t. Whenever the value can panic, unwrap, or just costs real work, reach for then, not then_some.

When to reach for which

Use then_some(value) when the value is already computed or trivially cheap — a literal, a copy, a field access. Use then(|| value) whenever building the value costs something or could panic. Both are stable (then since Rust 1.50, then_some since 1.62) and both read cleaner than the if/else they replace.

#097 Apr 2026

97. Option::take_if — Take the Value Out Only When You Want To

You want to pull a value out of an Option — but only if it meets some condition. Before take_if, that little sentence turned into a five-line dance with as_ref, is_some_and, and a separate take(). Now it’s one call.

The old dance

Say you hold an Option<Session> and you want to evict it if it’s expired, otherwise leave it alone. The naive version keeps ownership juggling in your face:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#[derive(Debug, PartialEq)]
struct Session { id: u32, age: u32 }

let mut slot: Option<Session> = Some(Session { id: 1, age: 45 });

// Old way: peek, decide, then take.
let evicted = if slot.as_ref().is_some_and(|s| s.age > 30) {
    slot.take()
} else {
    None
};

assert_eq!(evicted, Some(Session { id: 1, age: 45 }));
assert!(slot.is_none());

Three lines of control flow just to express “take it if it’s stale.” And if you ever need to inspect the value more deeply, you’re one borrow-checker nudge away from rewriting the whole thing.

Enter take_if

Option::take_if bakes the whole pattern into one method — it runs your predicate on the inner value, and if the predicate returns true, it take()s it for you:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let mut slot: Option<Session> = Some(Session { id: 2, age: 12 });

// Young session — predicate is false, nothing happens.
let evicted = slot.take_if(|s| s.age > 30);
assert_eq!(evicted, None);
assert!(slot.is_some());

// Age it and try again.
if let Some(s) = slot.as_mut() { s.age = 99; }
let evicted = slot.take_if(|s| s.age > 30);
assert_eq!(evicted.unwrap().id, 2);
assert!(slot.is_none());

One line, one branch, one name for the pattern.

The mutable twist

Here’s the detail that trips people up: the predicate receives &mut T, not &T. You can mutate the inner value before deciding whether to yank it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let mut counter: Option<u32> = Some(0);

// Bump on every call; take it once it hits the threshold.
for _ in 0..5 {
    let taken = counter.take_if(|n| {
        *n += 1;
        *n >= 3
    });
    if let Some(n) = taken {
        assert_eq!(n, 3);
    }
}

assert!(counter.is_none());

Useful for cache eviction with hit counters, retry-until-exhausted slots, or anywhere “inspect, maybe mutate, maybe remove” shows up. Reach for take_if whenever you find yourself writing if condition { x.take() } else { None }.

#058 Apr 2026

58. Option::is_none_or — The Guard Clause You Were Missing

You know is_some_and — it checks if the Option holds a value matching a predicate. But what about “it’s fine if it’s missing, just validate it when it’s there”? That’s is_none_or.

The Problem

You have an optional field and want to validate it only when present. Without is_none_or, this gets awkward fast:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
struct Query {
    max_results: Option<usize>,
}

fn is_valid(q: &Query) -> bool {
    // "None is fine, but if set, must be ≤ 100"
    match q.max_results {
        None => true,
        Some(n) => n <= 100,
    }
}

fn main() {
    assert!(is_valid(&Query { max_results: None }));
    assert!(is_valid(&Query { max_results: Some(50) }));
    assert!(!is_valid(&Query { max_results: Some(200) }));
}

That match is fine once, but when you’re validating five optional fields, it bloats your code.

After: is_none_or

Stabilized in Rust 1.82, is_none_or collapses the pattern into a single call — returns true if the Option is None, or applies the predicate if it’s Some:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct Query {
    max_results: Option<usize>,
    page: Option<usize>,
    prefix: Option<String>,
}

fn is_valid(q: &Query) -> bool {
    q.max_results.is_none_or(|n| n <= 100)
        && q.page.is_none_or(|p| p >= 1)
        && q.prefix.is_none_or(|s| !s.is_empty())
}

fn main() {
    let good = Query {
        max_results: Some(50),
        page: Some(1),
        prefix: None,
    };
    assert!(is_valid(&good));

    let bad = Query {
        max_results: Some(200),
        page: Some(0),
        prefix: Some(String::new()),
    };
    assert!(!is_valid(&bad));

    let empty = Query {
        max_results: None,
        page: None,
        prefix: None,
    };
    assert!(is_valid(&empty));
}

Three fields validated in three clean lines. Every None passes, every Some must satisfy the predicate.

is_some_and vs is_none_or — Pick the Right Default

Think of it this way — what happens when the value is None?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
    let val: Option<i32> = None;

    // is_some_and: None → false (strict: must be present AND valid)
    assert_eq!(val.is_some_and(|x| x > 0), false);

    // is_none_or: None → true (lenient: absence is acceptable)
    assert_eq!(val.is_none_or(|x| x > 0), true);

    let val = Some(5);

    // Both agree when value is present and valid
    assert_eq!(val.is_some_and(|x| x > 0), true);
    assert_eq!(val.is_none_or(|x| x > 0), true);

    let val = Some(-1);

    // Both agree when value is present and invalid
    assert_eq!(val.is_some_and(|x| x > 0), false);
    assert_eq!(val.is_none_or(|x| x > 0), false);
}

Use is_some_and when a value must be present. Use is_none_or when absence is fine — optional config, nullable API fields, or any “default-if-missing” scenario.

Real-World: Filtering with Optional Criteria

This pattern is perfect for search filters where users only set the criteria they care about:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct Filter {
    min_price: Option<f64>,
    category: Option<String>,
}

struct Product {
    name: String,
    price: f64,
    category: String,
}

fn matches(product: &Product, filter: &Filter) -> bool {
    filter.min_price.is_none_or(|min| product.price >= min)
        && filter.category.is_none_or(|cat| product.category == *cat)
}

fn main() {
    let products = vec![
        Product { name: "Laptop".into(), price: 999.0, category: "Electronics".into() },
        Product { name: "Book".into(), price: 15.0, category: "Books".into() },
        Product { name: "Phone".into(), price: 699.0, category: "Electronics".into() },
    ];

    let filter = Filter {
        min_price: Some(100.0),
        category: Some("Electronics".into()),
    };

    let results: Vec<_> = products.iter().filter(|p| matches(p, &filter)).collect();
    assert_eq!(results.len(), 2);
    assert_eq!(results[0].name, "Laptop");
    assert_eq!(results[1].name, "Phone");
}

Every None filter lets everything through. Every Some filter narrows the results. No match statements, no nested ifs — just composable guards.

37. Option::zip

Need to combine two Option values into a pair? Option::zip merges them into a single Option<(A, B)> — if either is None, you get None back.

The problem

You have two optional values and need both to proceed. The classic approach uses nested matching:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let name: Option<&str> = Some("Alice");
let age: Option<u32> = Some(30);

// Nested match — gets unwieldy fast
let greeting = match name {
    Some(n) => match age {
        Some(a) => Some(format!("{n} is {a} years old")),
        None => None,
    },
    None => None,
};

assert_eq!(greeting, Some("Alice is 30 years old".to_string()));

The fix: Option::zip

Zip collapses two Options into one tuple:

1
2
3
4
5
6
let name: Option<&str> = Some("Alice");
let age: Option<u32> = Some(30);

let greeting = name.zip(age).map(|(n, a)| format!("{n} is {a} years old"));

assert_eq!(greeting, Some("Alice is 30 years old".to_string()));

One line instead of six. If either value is None, zip short-circuits to None:

1
2
3
4
let name: Option<&str> = Some("Alice");
let age: Option<u32> = None;

assert_eq!(name.zip(age), None);

Bonus: zip with and_then

You can chain zip into more complex pipelines:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn lookup_user(id: u32) -> Option<String> {
    if id == 1 { Some("Alice".to_string()) } else { None }
}

fn lookup_role(id: u32) -> Option<String> {
    if id == 1 { Some("Admin".to_string()) } else { None }
}

let result = lookup_user(1)
    .zip(lookup_role(1))
    .map(|(user, role)| format!("{user} ({role})"));

assert_eq!(result, Some("Alice (Admin)".to_string()));

Option::zip is stable since Rust 1.46 and works anywhere you need both-or-nothing semantics without the nesting.

#027 Jan 2023

27. Option's sum/product

Rust’s option implements Sum and Product traits too!

Use it when you want to get None if there is a None element and sum of values otherwise.

1
2
3
let nums: [Option<u32>;3] = [Some(1), Some(10), None];
let maybe_nums: Option<u32> = nums.into_iter().sum();
assert_eq!(maybe_nums, None);

or sum of the values …

1
2
3
let nums = [Some(1), Some(10), Some(100)];
let maybe_nums: Option<u32> = nums.into_iter().sum();
assert_eq!(maybe_nums, Some(111));

And product.

1
2
3
let nums = [Some(1), Some(10), Some(100)];
let maybe_nums: Option<u32> = nums.into_iter().product();
assert_eq!(maybe_nums, Some(1000));
#026 Jan 2023

26. Collecting into Option

Rust’s option implements FromIterator too!

Use it when you want to get None if there is a None element and values otherwise.

1
2
3
let cars = [Some("Porsche"), Some("Ferrari"), None];
let maybe_cars: Option<Vec<_>> = cars.into_iter().collect();
assert_eq!(maybe_cars, None);

or values …

1
2
3
let cars = [Some("Porsche"), Some("Ferrari"), Some("Skoda")];
let maybe_cars: Option<Vec<_>> = cars.into_iter().collect();
assert_eq!(maybe_cars, Some(vec!["Porsche", "Ferrari", "Skoda"]));
#025 Dec 2022

25. Option's iterator

Rust’s option implements an iterator!

1
2
3
4
let o: Option<u32> = Some(200u32);
let mut iter = o.into_iter();
let v = iter.next();
assert_eq!(v, Some(200u32));

Why is this useful? see example.

1
2
3
4
5
6
let include_this_pls = Some(300u32);
let r: Vec<u32> = (0..2).chain(include_this_pls).chain(2..5).collect();
assert_eq!(r, vec![0,1,300,2, 3,4]);
let but_not_this = None;
let r: Vec<u32> = (0..2).chain(but_not_this).chain(2..5).collect();
assert_eq!(r, vec![0,1,2,3,4]);
#024 Dec 2022

24. ..=X

As of 1.66, it is possible to use ..=X in patterns

1
2
3
4
5
6
let result = 20u32;

match result {
    0..=20 => println!("Included"),
    21.. => println!("Sorry"),
}
#023 Dec 2022

23. Enum's default value

Instead of manually implementing Default trait for an enum, you can derive it and explicitly tell which variant should be the default one.

1
2
3
4
5
6
7
8
9
#[derive(Default)]
enum Car{
    #[default]
    Porsche,
    Ferrari,
    Skoda
}

let car = Car::default();
#022 Nov 2022

22. Enum's Debug

Use Debug trait to print enum values if needed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#[derive(Default,Debug)]
enum Car{
    #[default]
    Porsche,
    Ferrari,
    Skoda
}

let car = Car::default();
println!("{:?}", car);
#021 Oct 2022

21. Zip longest

Sometimes there is a need to zip two iterables of various lengths.

If it is known which one is longer, then use the following approach:

First one must be longer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let nums1 = [1, 2, 3, 4];
let nums2 = [10, 20];

for (n1, n2) in nums1
    .into_iter()
    .zip(nums2.into_iter().chain(std::iter::repeat(0i32)))
{
    println!("{n1} - {n2}");
}
// Output:
// 1 - 10
// 2 - 20
// 3 - 0
// 4 - 0
#020 Sep 2022

20. let-else statements

As of 1.65, it is possible to use let statement with a refutable pattern.

1
2
3
4
5
6
7
let result: Result<i32, ()> = Ok(20);

let Ok(value) = result else {
  panic!("Heeeelp!!!");
};

assert_eq!(value, 20);
#019 Aug 2022

19. breaking from labeled blocks

As of 1.65, it is possible to label plain block expression and terminate that block early.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let result = 'block: {
    let result = 20i32;
    if result < 10 {
        break 'block 1;
    }
    if result > 10 {
        break 'block 2;
    }
    3
};
assert_eq!(result, 2);
#018 Jul 2022

18. flatten options

Use flatten to iterate over only Some values if you have a collection of Options.

1
2
3
4
5
6
let nums = vec![None, Some(2), None, Some(3), None];

let mut iter = nums.into_iter().flatten();

assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(3));
#016 Jun 2022

16. Option/Result match?!

Try to avoid matching Option or Result.

Use if let instead.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let result = Some(111);

// Not nice
match result {
    Some(x) => println!("{x}"),
    None => {}
};

// Better
if let Some(x) = result {
    println!("{x}");
}
#004 Jan 2022

4. Option -> Result

Convert Option to Result easily.

1
2
3
let o: Option<u32> = Some(200u32);
let r: Result<u32,()> = o.ok_or(());
assert_eq!(r, Ok(200u32));
#003 Jan 2022

3. Result -> Option

Convert Result to Option easily.

1
2
3
let r: Result<u32,()> = Ok(200u32);
let o: Option<u32> = r.ok();
assert_eq!(o, Some(200u32));