Reaching for a four-line match just to turn an Option into a plain value? map_or does the transform and the fallback in a single call.
The match you keep rewriting
You have an Option, you want a concrete value: apply a function if it’s Some, fall back to a default if it’s None.
1
2
3
4
5
6
7
| let name: Option<&str> = Some("ferris");
let len = match name {
Some(n) => n.len(),
None => 0,
};
assert_eq!(len, 6);
|
map_or(default, f) collapses both arms. The first argument is the None fallback, the second is what to do with the value inside Some:
1
2
3
4
5
| let name: Option<&str> = Some("ferris");
assert_eq!(name.map_or(0, |n| n.len()), 6);
let missing: Option<&str> = None;
assert_eq!(missing.map_or(0, |n| n.len()), 0);
|
It beats the common .map(|n| n.len()).unwrap_or(0) too — same result, but no intermediate Option built just to immediately unwrap it.
The catch: the default is eager
map_or takes the default by value, so it’s computed whether or not you need it. With a cheap literal like 0 that’s free. With anything that allocates or does real work, you pay for it even on the Some path:
1
2
3
4
5
6
7
8
9
10
| fn expensive_default() -> String {
// imagine a config read, an allocation, a computation...
"fallback".to_string()
}
let port: Option<u16> = Some(8080);
// ⚠️ expensive_default() runs even though port is Some
let label = port.map_or(expensive_default(), |p| format!("port {p}"));
assert_eq!(label, "port 8080");
|
When the fallback isn’t free, switch to map_or_else, which takes a closure and only calls it on None:
1
2
3
4
5
6
7
| fn expensive_default() -> String {
"fallback".to_string()
}
let port: Option<u16> = None;
let label = port.map_or_else(|| expensive_default(), |p| format!("port {p}"));
assert_eq!(label, "fallback");
|
It works on Result too
Result::map_or follows the same shape — the value goes through f, any Err yields the default:
1
2
3
4
5
| let parsed = "42".parse::<i32>();
assert_eq!(parsed.map_or(-1, |n| n * 2), 84);
let bad = "oops".parse::<i32>();
assert_eq!(bad.map_or(-1, |n| n * 2), -1);
|
Use map_or when the default is a cheap literal, map_or_else when it isn’t, and let the match go.