Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I pattern match against an Option<String>?

I can straight-forwardly match a String in Rust:

let a = "hello".to_string();

match &a[..] {
    "hello" => {
        println!("Matches hello");
    }
    _ => panic!(),
}

If I have an option type, it fails:

match Some(a) {
    Some("hello") => {
        println!("Matches some hello");
    }
    _ => panic!(),
}

because the types don't match:

error[E0308]: mismatched types
 --> src/main.rs:5:14
  |
5 |         Some("hello") => {
  |              ^^^^^^^ expected struct `std::string::String`, found reference
  |
  = note: expected type `std::string::String`
             found type `&'static str`

I can't do the [..] trick because we have an Option. The best that I have come up with so far is:

match Some(a) {
    Some(b) => match (&b[..]) {
        "hello" => {
            println!("Matches some, some hello");
        }
        _ => panic!(),
    },
    None => panic!(),
}

which works but is terrible for its verbosity.

In this case, my code is just an example. I do not control the creation of either the String or the Some(String) — so I can't change this type in reality as I could do in my example.

Any other options?

like image 433
Phil Lord Avatar asked Dec 30 '17 12:12

Phil Lord


People also ask

How do you match a pattern in Scala?

A pattern match includes a sequence of alternatives, each starting with the keyword case. Each alternative includes a pattern and one or more expressions, which will be evaluated if the pattern matches. An arrow symbol => separates the pattern from the expressions.

How does match work in Rust?

Rust has an extremely powerful control flow construct called match that allows you to compare a value against a series of patterns and then execute code based on which pattern matches.

What is pattern matching syntax?

Patterns are written in JME syntax, but there are extra operators available to specify what does or doesn't match. The pattern-matching algorithm uses a variety of techniques to match different kinds of expression.

Which method of case class allows using objects in pattern matching?

Case classes are Scala's way to allow pattern matching on objects without requiring a large amount of boilerplate. In the common case, all you need to do is add a single case keyword to each class that you want to be pattern matchable.


3 Answers

It's a known limitation of Rust's patterns.

Method calls (including internal methods for operators like ==) automatically call .deref() as needed, so String gets automagically turned into &str for comparisons with literals.

On the other hand, the patterns are quite literal in their comparisons, and find that String and &str are different.

There are two solutions:

  1. Change Option<String> to Option<&str> before matching on it: Some(a).as_deref(). The as_deref() is a combo of as_ref() that makes Option<&String> (preventing move), and deref()/as_str() then unambiguously references it as a &str.

  2. Use match guard: match Some(a) { Some(ref s) if s == "hello" => … }. Some(ref s) matches any String, and captures it as s: &String, which you can then compare in the if guard which does the usual flexible coercions to make it work.

See also:

  • Converting from Option<String> to Option<&str>
like image 152
Kornel Avatar answered Oct 20 '22 14:10

Kornel


As of Rust 1.40, you can now call as_deref on Option<String> to convert it to Option<&str> and then match on it:

match args.nth(1).as_deref() {
    Some("help") => {}
    Some(s) => {}
    None => {}
}

I found this because it is one of the clippy lints.

like image 32
Deadbeef Avatar answered Oct 20 '22 14:10

Deadbeef


Look at this.

You cannot match on std::String, as you've found, only on &str. Nested pattern matches work, so if you can match on &str, you can match on Option<&str>, but still not on Option<String>.

In the working example, you turned the std::String into a &str by doing &a[..]. If you want to match on a Option<String>, you have to do the same thing.

One way is to use nested matches:

match a {
    Some(ref s) => match &s[..] {
        "hello" => /* ... */,
        _ => /* ... */,
    },
    _ => /* ... */,
}

But then you have to duplicate the "otherwise" code if it's the same, and it's generally not as nice.

Instead, you can turn the Option<String> into an Option<&str> and match on this, using the map function. However, map consumes the value it is called on, moving it into the mapping function. This is a problem because you want to reference the string, and you can't do that if you have moved it into the mapping function. You first need to turn the Option<String> into a Option<&String> and map on that.

Thus you end up with a.as_ref().map(|s| /* s is type &String */ &s[..]). You can then match on that.

match os.as_ref().map(|s| &s[..]) {
    Some("hello") => println!("It's 'hello'"),
    // Leave out this branch if you want to treat other strings and None the same.
    Some(_) => println!("It's some other string"),
    _ => println!("It's nothing"),
}
like image 5
Sebastian Redl Avatar answered Oct 20 '22 13:10

Sebastian Redl