rustreferencematchborrow

Why are match statements more picky about reference types than function parameters?


In Rust, one often sees functions that take &str as a parameter.

fn foo(bar: &str) {
    println!("{}", bar);
}

When calling functions like this, it is perfectly fine to pass in a String as an argument by referencing it.

let bar = String::from("bar");
foo(&bar);

Strictly speaking, the argument being passed is an &String and the function is expecting an &str, but Rust (as one would hope) just figures it out and everything works fine. However, this is not the case with match statements. If I try and use the same bar variable as before in a match statement, the naive usage will not compile:

match &bar {
    "foo" => println!("foo"),
    "bar" => println!("bar"),
    _ => println!("Something else")
};

Rustc complains that it was expecting an &str but received an &String. The problem and solution are both very obvious: just borrow bar more explicitly with .as_str(). But this brings me to the real question: why is this the case?

If Rust can figure out that an &String trivially converts to an &str in the case of function arguments, why can't it do the same thing with match statements? Is this the result of a limitation of the type system, or is there hidden unsafety in fancier borrowing with match statements? Or is this simply a case of a quality of life improvement getting integrated into some places but not others? I'm sure someone knowledgeable about type systems has an answer, but there seems to be very little information about this little quirk of behavior on the internet.


Solution

  • The technical reason it doesn't work is because the match scrutinee is not a coercion site. Function arguments, as shown in your foo(&bar) example, are possible coercion sites; and it allows you to pass a &String as a &str because of Deref coercion.

    A possible reason why its not a coercion site is that there's no clear type that it should be coerced to. In your example you'd like it to be &str since that matches the string literals, but what about:

    match &string_to_inspect {
        "special" => println!("this is a special string"),
        other => println!("this is a string with capacity: {}", other.capacity()),
    };
    

    One would like the match to act like &str to match the literal, but because the match is on a &String one would expect other to be a &String as well. How to satisfy both? The next logical step would be for each pattern to coerce as required, which has been much desired... but it opens a whole can of worms since Deref is user-definable. See deref patterns from the Rust lang-team for more info.