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.
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_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.