Unnecessary parentheses are actually necessary?

#1

I’m exploring some of the finer points of pattern matching to improve my mental model of what some of the more complicated syntaxes actually mean (in order to better understand what the compiler complains about, as opposed to just throwing some & / mut / ref into the code, tossing around and hoping for the best).

In doing so, I’ve come across this (admittedly rather useless) example, in which foo's argument is pattern-matched to extract its value into the bar parameter while making the bar binding mutable (if that’s the correct way of putting things):

fn foo(&(mut bar): &i32) {
    println!("{}", bar);
    bar = 43;
    println!("{}", bar);
}

fn main() {
    let s = 42;
    foo(&s);
}

When I compile this, I get a warning that the parens around mut bar are unnecessary:

warning: unnecessary parentheses around pattern
 --> src/main.rs:1:9
  |
1 | fn foo(&(mut bar): &i32) {
  |         ^^^^^^^^^ help: remove these parentheses
  |
  = note: #[warn(unused_parens)] on by default

But as far as I can see, they aren’t – if I remove them, the code doesn’t compile anymore, because the meaning changes to “pattern-match against a mutable reference”, as opposed to the earlier “pattern-match against a shared reference, and make the resulting binding mutable”.

Or am I understanding this wrong / missing something?

(Note that if you want to pattern-match against a mutable reference and make the resulting binding mutable, parentheses are indeed not needed, fn foo(&mut mut bar: &mut i32) seems to both parse and typecheck just fine.)

1 Like
#2

Your understanding is correct. This look like a bug in the unused_parens lint. It should not warn in this case.

Update: This is https://github.com/rust-lang/rust/issues/55342

4 Likes
#3

Thank you very much for the link! It should have occurred to me this might be a bug and already reported, I could have saved you and myself some typing :slight_smile: However, it was still very helpful to get feedback on whether I understand what’s going on, so thanks for that as well!

#4

(By the way, &self and &mut self are not destructuring pattern matching notations (imho one of the very few inconsistencies of the language))

2 Likes
#5

one of the very few inconsistencies of the language

Agreed, but TBH, this feature in particular didn’t trip me up all that much :slight_smile: It’s easy to understand even for a beginner (I think) why self arguments would have some special bit of syntax to make them more ergonomic.

By comparison, it took me much longer to grok that when I have a reference to a struct (let foo: &Foo = ...), I still have to take references to its members explicitly (let bar = &foo.bar), or else Rust will think I’m trying to move bar out of foo, which only works for Copy types (when you have a reference). I somehow thought that once foo is a reference, anything I can get out of it will necessarily be a reference too, and let bar = &foo.bar felt redundant, because I thought it meant let bar = (&foo).bar (as if the compiler was forcing me to re-take a reference to Foo), whereas it actually means let bar = &(foo.bar).

I mean, the compiler errors have been there the whole time ("cannot move out of borrowed value"™), I just didn’t really realize what they meant.

#6

Yep, the dot operator in rust performs auto-dereferencing, i.e., can be equivalent to C/C++'s -> arrow operator: let x = &(27, 42); x.1 is actually sugar for let x = &(27, 42); (*x).1; hence the need for the explicit outer &, when you just want a reference to a field (e.g., because that field is not Copy)

1 Like