#058 Apr 3, 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.

← Previous 57. New Math Constants — GOLDEN_RATIO and EULER_GAMMA in std Next → 59. split_first_chunk — Destructure Slices into Arrays