简体   繁体   中英

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?

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:

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 .

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"),
}

In some cases, you can use unwrap_or to replace Option::None with a predefined &str you don't want to handle in any special way.

I used this to handle user inputs:

let args: Vec<String> = env::args().collect();

match args.get(1).unwrap_or(&format!("_")).as_str() {
    "new" => {
        print!("new");
    }
    _ => {
        print!("unknown string");
    }
};

Or to match your code:

let option = Some("hello");

match option.unwrap_or(&format!("unhandled string").as_str()) {
    "hello" => {
        println!("hello");
    }
    _ => {
        println!("unknown string");
    }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM