0

I am having trouble trying to understand pattern matching rules in Rust. I originally thought that the idea behind patterns are to match the left-hand side and right-hand side like so:

struct S {
    x: i32,
    y: (i32, i32)
}
let S { x: a, y: (b, c) } = S { x: 1, y: (2, 3) }; 
// `a` matches `1`, `(b, c)` matches `(2, 3)`

However, when we want to bind a reference to a value on the right-hand side, we need to use the ref keyword.

let &(ref a, ref b) = &(3, 4);

This feels rather inconsistent.

Why can't we use the dereferencing operator * to match the left-hand side and right-hand side like this?

let &(*a, *b) = &(3, 4);
// `*a` matches `3`, `*b` matches `4`

Why isn't this the way patterns work in Rust? Is there a reason why this isn't the case, or have I totally misunderstood something?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
vbstb
  • 1,261
  • 1
  • 10
  • 14
  • Here you actually need to designate a place where the value is loaded. dereferencing would give the value stored at the location, not the location itself. At least that was my impression when I first met this. – g_bor Oct 01 '19 at 12:23

2 Answers2

2

Using the dereferencing operator would be very confusing in this case. ref effectively takes a reference to the value. These are more-or-less equivalent:

let bar1 = &42;
let ref bar2 = 42;

Note that in let &(ref a, ref b) = &(3, 4), a and b both have the type &i32 — they are references. Also note that since match ergonomics, let (a, b) = &(3, 4) is the same and shorter.

Furthermore, the ampersand (&) and asterisk (*) symbols are used for types. As you mention, pattern matching wants to "line up" the value with the pattern. The ampersand is already used to match and remove one layer of references in patterns:

let foo: &i32 = &42;

match foo {
    &v => println!("{}", v),
}

By analogy, it's possible that some variant of this syntax might be supported in the future for raw pointers:

let foo: *const i32 = std::ptr::null();

match foo {
    *v => println!("{}", v),
}

Since both ampersand and asterisk could be used to remove one layer of reference/pointer, they cannot be used to add one layer. Thus some new keyword was needed and ref was chosen.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
0

In this specific case, you can achieve the same with neither ref nor asterisk:

fn main() {
    let (a, b) = &(3, 4);

    show_type_name(a);
    show_type_name(b);
}

fn show_type_name<T>(_: T) {
    println!("{}", std::any::type_name::<T>()); // rust 1.38.0 and above
}

It shows both a and b to be of type &i32. This ergonomics feature is called binding modes.

But it still doesn't answer the question of why ref pattern in the first place. I don't think there is a definite answer to that. The syntax simply settled on what it is now regarding identifier patterns.

edwardw
  • 12,652
  • 3
  • 40
  • 51