Option::is_some_and and let-else
Published 9 October 2026
What it does¶
Option::is_some_and returns true only if the option is Some and the value inside
passes a predicate. None is always false.
The implementation is a two-arm match:
f runs at most once, so FnOnce is enough (same reasoning as map, see
Revision 2026-10-02).
The three cases¶
let age: Option<u32> = Some(25);
assert!(age.is_some_and(|a| a >= 18)); // true
let age: Option<u32> = Some(12);
assert!(!age.is_some_and(|a| a >= 18)); // false: predicate fails
let age: Option<u32> = None;
assert!(!age.is_some_and(|a| a >= 18)); // false: it's None
What it replaces¶
// before
if let Some(a) = age { if a >= 18 { /* ... */ } }
matches!(age, Some(a) if a >= 18)
age.map_or(false, |a| a >= 18)
// now
age.is_some_and(|a| a >= 18)
Clippy flags the map_or(false, ...) form:
warning: this `map_or` can be simplified
= note: `#[warn(clippy::unnecessary_map_or)]` on by default
help: use `is_some_and` instead
It consumes the Option¶
Takes self by value
For a Copy type like Option<u32> that’s invisible. For Option<String> the call
moves the option, and it’s gone afterwards.
let name: Option<String> = Some("Karthik".to_string());
let _ = name.is_some_and(|s| s.starts_with('K'));
println!("{name:?}"); // error[E0382]: borrow of moved value: `name`
Borrow first with as_ref() (Option<&String>) or as_deref() (Option<&str>):
let name: Option<String> = Some("Karthik".to_string());
assert!(name.as_ref().is_some_and(|s| s.starts_with('K')));
assert!(!name.as_deref().is_some_and(|s| s.is_empty()));
println!("{name:?}"); // still usable
Examples where it reads well¶
Methods that already return Option<&T>, so no as_ref() needed:
let v = vec![1, 2, 3];
assert!(v.last().is_some_and(|&x| x == 3));
assert!(v.first().is_some_and(|x| *x == 1));
let words = vec!["apple", "", "kiwi"];
let first_long = words.iter().find(|w| w.len() > 4);
assert!(first_long.is_some_and(|w| w.starts_with('a')));
Passing a function path instead of a closure:
A field check in an if:
The counterpart: is_none_or¶
is_none_or (stable since Rust 1.82) is the “vacuously true” version: true if the
option is None or the predicate holds.
fn within_limit(n: u32, limit: Option<u32>) -> bool {
limit.is_none_or(|l| n <= l) // no limit, or within it
}
assert!(within_limit(5, None));
assert!(within_limit(5, Some(10)));
assert!(!within_limit(15, Some(10)));
self |
predicate | is_some_and |
is_none_or |
|---|---|---|---|
None |
— | false |
true |
Some(x) |
true |
true |
true |
Some(x) |
false |
false |
false |
Result has the same pair¶
is_ok_and and is_err_and. They also take self by value, so use as_ref() to keep
the Result:
let r: Result<u32, String> = "42".parse::<u32>().map_err(|e| e.to_string());
assert!(r.as_ref().is_ok_and(|&n| n > 40));
let e: Result<u32, String> = Err("timeout".into());
assert!(e.as_ref().is_err_and(|m| m.contains("time")));
is_some_and, is_ok_and and is_err_and were stabilized in Rust 1.70.
Related pattern: let-else¶
Another way to deal with an Option without nesting. This looks odd the first time you see it:
let Some(ticket) = self.tickets.get(ticket_id) else {
return false;
};
// `ticket` is in scope here, already unwrapped
This is let-else, stable since Rust 1.65. It’s a guard clause. Rust tries to match the pattern
Some(ticket). If it matches, ticket is bound in the enclosing scope and execution
continues. If it doesn’t, the else block runs.
The else block must diverge
It has to return, break, continue, panic!, or otherwise never fall through.
Otherwise ticket would be unbound on the next line. The compiler enforces this:
What it replaces¶
// 1. match: verbose, the binding comes out through an expression
let ticket = match self.tickets.get(ticket_id) {
Some(t) => t,
None => return false,
};
// 2. if let: the happy path ends up nested inside the block
if let Some(ticket) = self.tickets.get(ticket_id) {
// ...everything indented here
} else {
return false;
}
let-else keeps the early exit at the top and the rest of the function flat. It’s
Swift’s guard let.
Why not ? The function returns bool, not Option/Result, so there is nothing
for ? to propagate into.
Any refutable pattern works¶
let Ok(n) = s.parse::<u32>() else { return Err(format!("bad number: {s}")) };
let [first, .., last] = slice else { return None }; // needs at least 2 elements
for ev in events {
let Event::Click { x, y } = ev else { continue };
// only clicks get here
}
is_some_and or let-else?¶
- You only need a yes/no answer:
opt.is_some_and(|x| ...). - You need the value for the rest of the function:
let Some(x) = opt else { ... };.