thisisrandy
thisisrandy

Reputation: 3075

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

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.

Upvotes: 34

Views: 22194

Answers (9)

shape warrior t
shape warrior t

Reputation: 131

As of Rust 1.82.0, we have Option::is_none_or for this exact scenario:

foo.is_none_or(|foo_val| foo_val < 5)

Upvotes: 3

u18056049
u18056049

Reputation: 101

The matches! macro seems like a good fit:

if matches!(foo, Some(a) if a>=5) { foo.replace(5) }

Rust Playground

Upvotes: 6

Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 88696

Since Rust 1.70.0 (2023-06-02), Option::is_some_and can be used. It has been built for the opposite use case as in the question: returning false if it is None:

if foo.is_some_and(|foo_val| foo_val < 5) { 
    // ...
}

Upvotes: 6

at54321
at54321

Reputation: 11728

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.

Rust 1.70+

The is_some_and method has been stabilized in Rust 1.70. Here is an example:

if name.is_some_and(|s| s.len() > 50) {
    println!("This is a long name!");
}

In many cases that should be more readable than map_or / map_or_else.

Upvotes: 30

Sandeep
Sandeep

Reputation: 21144

With filter and or,

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

Upvotes: 3

Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 88696

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

For more information see Option::map_or.

Upvotes: 65

Paolo Falabella
Paolo Falabella

Reputation: 25844

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));

Upvotes: 3

Jmb
Jmb

Reputation: 23319

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
}

Upvotes: 15

MaxV
MaxV

Reputation: 2810

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

Upvotes: 6

Related Questions