What is the idiomatic way to do something when an Option is either None, or the inner value meets some condition?

Viewed 4593

Is there a more idiomatic way to express something like the following?

fn main() {
    let mut foo: Option<u8> = None;
    match foo {
        Some(foo_val) if ! (foo_val < 5) /* i.e. the negation of my acceptance condition */ => {}
        _ => { foo.replace(5); }
    }
}

It seems like most of the time there's an alternative to having an arm that doesn't do anything, but I've been unable to find one for this particular case.

What I'd like to say is the more direct if foo.is_none() || /* some way to extract and test the inner value */ { ... }, or perhaps some chaining trick that's eluding me.

7 Answers
//        in None case
//             │       in Some(_) case
//            ┌┴─┐  ┌───────────────────┐    
if foo.map_or(true, |foo_val| foo_val < 5) {
    // ...
}

For more information see Option::map_or.

There are many ways to do it. One of the simplest (and arguably most readable) is something like this:

if foo.unwrap_or(0) < 5 {
    ...
}

The above will be true in both cases:

  • when foo is Some with a value smaller than 5;
  • when foo is None.

In some more complex scenarios, where the "default" value needs to be calculated and performance is critical, you might want to consider unwrap_or_else.

As Lukas suggested, the map_or method can also be used. Note that arguments passed to map_or are eagerly evaluated, so if performance is critical, you might want to consider map_or_else as an alternative.

You can do it with filter (using the negation of your condition) and is_none:

if foo.filter(|&x| !(x < 5)).is_none() {
    // Here either foo was None or it contained a value less than 5
}

I'm not sure I completely understand your question but you can try something like that:

fn main() {
    let foo: Option<u8> = None;
    let result = foo.filter(|foo_val| !(*foo_val < 5) ).unwrap_or(5);
    println!("Result: {result}");
}

More example on Playground

I'll throw in another solution just for fun....

foo = foo.
    or(Some(5)). // if None return Some(5)
    map(|x| if x<5 { 5 } else { x });

or (for this specific example)

foo = foo.
    or(Some(5)). // if None return Some(5)
    map(|x| u8::max(x, 5));

With filter and or,

foo = foo.filter(|a|  *a >= 5)
                .or(Some(5));
Related