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.