Option

#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));