10

I have a tiny playground example here

fn main() {
    let l = Some(3);
    match &l {
        None => {}
        Some(_x) => {} // x is of type &i32
    }
}

I'm pattern matching on &Option and if I use Some(x) as a branch, why is x of type &i32?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
nz_21
  • 6,140
  • 7
  • 34
  • 80

1 Answers1

19

The type of the expression &l you match against is &Option<i32>, so if we are strict the patterns should be &None and &Some(x), and if we use these patterns, the type of x indeed is i32. If we omit the ampersand in the patterns, as you did in your code, it first looks like the patterns should not be able to match at all, and the compiler should throw an error similar to "expected Option, found reference", and indeed this is what the compiler did before Rust version 1.26.

Current versions of Rust support "match ergonomics" introduced by RFC 2005, and matching a reference to an enum against a pattern without the ampersand is now allowed. In general, if your match expression is only a reference, you can't move any members out of the enum, so matching a reference against Some(x) is equivalent to matching against the pattern &Some(ref x), i.e. x becomes a reference to the inner value of the Option. In your particular case, the inner value is an i32, which is Copy, so you would be allowed to match against &Some(x) and get an i32, but this is not possible for general types.

The idea of the RFC is to make it easier to get the ampersands and refs in patterns right, but I'm not completely convinced whether the new rules actually simplified things, or whether they added to the confusion by making things magically work in some cases, thereby making it more difficult for people to get a true understanding of the underlying logic. (This opinion is controversial – see the comments.)

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 2
    Since this answer is widely linked, maybe it would be time to revisit the last paragraph? Match ergonomics seem to have stood the test of time, with the difference between `match enum` and `match &enum` now being described as analogous to difference between `for x in container` and `for x in &container`, as the latter also yields references without consuming the container. Match ergonomics especially shine with custom enums which don't have `as_ref()`. I find being able to `match &my_enum` and get the references without littering every arm with `&` and `ref` to be a tangible improvement. – user4815162342 Dec 23 '21 at 13:17
  • 4
    @user4815162342 I personally don't feel the `ref`s and `&`s "litter" match arms, but rather carry valuable information when reading the code. Figuring out the type of a target variable in a match arm when reading code now requires more mental energy from me than it used to. I agree it's slighlty easier to _write_ the code, but production code is typically far more often read than written, so I'm still not convinced. The `for x in container` vs `&container` thing is an API convention in the standard library, while match ergonomics are part of the language – I don't think they are comparable. – Sven Marnach Dec 25 '21 at 21:55
  • The `&` in my view litters the code because it must be repeated for every single match arm. (`ref` must be in every arm that captures something, so it's not quite as bad, though it gets repetitive too.) It adds no new information for the reader because it's already clear we're matching on a reference, it's just ceremony to appease the compiler. But I realize that it's also a matter of taste and that we don't agree on that. As for the analogy with `for`, I agree that they're not comparable implementation-wise, but they're comparable conceptually and a good way to grok matching on reference. – user4815162342 Dec 25 '21 at 22:25
  • 1
    @user4815162342 It's never been necessary to repeat `&` in every match arm, since it's possible to dereference the match expression instead. Otherwise I agree that this is a matter of taste, and I do use match ergonomics in my own code, in particular if it's code no one else needs to read. Historically, there was a trend to make more and more things magically "just work" in programming languages (e.g. implicit type conversions in C), culminating in weakly typed languages like JavaScript. This trend was reversed in more modern languages including Rust, and I personally like that direction. – Sven Marnach Dec 28 '21 at 21:14
  • Just to clarify what I meant by repeating `&` in every match arm, I referred to situations like [this one](https://godbolt.org/z/hn6T81qK7) where it seems to be necessary and I'm not sure how dereferencing the match expression helps (or what it even means). – user4815162342 Dec 28 '21 at 21:42
  • @user4815162342 Sorry, that was kind of unclear. What I meant is to just write `match *foo`, and then omit the ampersand in each branch, which works fine in the example you linked. – Sven Marnach Dec 28 '21 at 22:37
  • Thanks, that pattern had completely slipped my mind. But I must admit (and I'm really not saying that to support my case), `match *foo` totally looks like it should be moving out of `foo`. – user4815162342 Dec 29 '21 at 20:34
  • @user4815162342 I guess this brings us back to [the question that brought you here in the first place](https://stackoverflow.com/a/70451764). :) – Sven Marnach Dec 31 '21 at 13:28