The difference meaning of '&' in match expression

Hello community,
I'm trying to iterate a vector in match expression, but i don't know what the difference if I add & at the second element of the tuple.

let nums = vec![100, 200, 300, 400, 500];
let mut index_hashmap = HashMap::with_capacity(nums.len());
for (idx, &n) in nums.iter().enumerate() {
let nums = vec![100, 200, 300, 400, 500];
let mut index_hashmap = HashMap::with_capacity(nums.len());
for (idx, n) in nums.iter().enumerate() {

I hear people said the first one will trigger the clone() method, but i can't understand why.
:slightly_frowning_face:

The part right after the for is a pattern where you are matching the structure of a value, and any identifier takes on the value at that place in the pattern. This is sometimes called destructuring.

With (idx, &n), you are matching a tuple, assigning the first element to idx, matching a reference in the second element and assigning the value behind the reference to n.

With (idx, n), it's the same without destructuring the reference, so n instead gets the value of that reference itself.

These assignments to idx and n work as moves, or copies if their respective types implement Copy. The enumerated usize can be copied, as can the reference in the second case. You didn't show enough to know the type behind the reference in the first case (&n), but this will have to be something copyable or it's an error, because you can't move out of a reference.

In any case, there's no clone(), as that's always an explicit call in Rust.

6 Likes

Given

let x = 42; // It needs to be Copy
let at_x = &x;
// or equivalently,
let ref at_x = x; // bind by reference

Then,

let &y = at_x;

is equivalent to:

let y = *at_x;

That is,

When destructuring a pattern with &_, we are effectively dereferencing the expression the pattern was matched against.

The mental model is the following:
in the same way that when you have -x = -3, it follows that x = 3,
in Rust when you have &y = &42, it follows that y = 42.


Finally, when that dereferencing pattern is used to bind a value to a variable, then the type of that value must be Copyable (as is the case with the dereferencing operator *).

But the great thing with pattern matching is that you can nest it, thus being able to combine the dereferencing pattern (&_) with the binding-by-reference pattern (ref _), thus being able to avoid needing Copy:

let s = String::from("not Copy");
let at_s = &s;

let &(ref z) = at_s;
// <=> let &(ref z) = &s;
// <=> let ref z = s;
// <=> let z = &s;
let _: &String = z;

If the previous example seems contrived, the idea is that in between the initial & .. and final ref varname, more complex patterns can be present, such as:

  • Option<T>:

    fn option_as_ref (
        at_s_opt: &'_ Option<String>,
    ) -> Option<&'_ String>
    {
         if let &Some(ref at_s) = at_s_opt {
      // if let Some(ref at_s) = *at_s_opt {
             Some(at_s)
         } else {
             None
         }
    }
    
  • tuples, such as (T, T):

    let array_of_vec_pairs = [
         (vec![1, 1], vec![1, 2]),
         (vec![2, 1], vec![2, 2]),
    ];
    for &(ref v1, ref v2) in &array_of_vec_pairs {
        let _: &Vec<_> = v1;
        // ...
    }
    
5 Likes

@Yandros For me the combination of & and ref are confusing.

let &(ref z) = at_s;
let _: &String = z;

will give a compiler warning:

warning: unnecessary parentheses around pattern
 --> src/main.rs:7:10
  |
7 |     let &(ref z) = at_s;
  |          ^^^^^^^ help: remove these parentheses
  |
  = note: #[warn(unused_parens)] on by default

And this seems to be functionally equivalent:

let ref z = at_s;
let _: &String = z;

If I understand it correctly, you only use & on the LHS if it's for a type, like this:

let s: &String = x;

But not in front of a variable name. If you want to indicate that you want to pattern match or destruct, but just have a reference, not a copy or move out the field or Variant, then you can use ref on the LHS before a variable name.

Actually, I'm wondering, is there any situation where you really need the ref keyword, eg, where it can't be done by putting a & on the RHS. Maybe destructuring some fields as mut and others as immutable?

I'm just running into this exact thing. The following two are equivalent:

match &mut self.outgoing
{
	Some( out ) => await!( out.send( msg ) )                             ,
	None        => Err( ThesError::PeerSendAfterCloseConnection.into() ) ,
}

and:

match self.outgoing
{
	Some( ref mut out ) => await!( out.send( msg ) )                             ,
	None                 => Err( ThesError::PeerSendAfterCloseConnection.into() ) ,
}

Both of these will solve a "cannot move out of borrowed content" error, but for the beginning rust programmer just desperately trying to get stuff to compile, this does get rather confusing. Also because ref is quite rare, compared to &.

Especially, when using neither the compiler will issue 2 errors, suggesting &self.outgoing to solve the moving out of borrwed content, and using mut out, yes, without the ref to solve the need for mutability. That combination as suggested does not compile. erk!

To make matters potentially worse, if the type is copy, it will never suggest you that taking a reference is good enough, but then let's hope Copy types usually are no bigger than a pointer anyways, so it probably don't matter.

thanks! :smile:

Yep:

fn option_as_ref_original<T> (x_opt: &'_ Option<T>) -> Option<&'_ T>
{
    match *x_opt {
        Some(ref at_x) => Some(at_x),
        None => None,
    }
}

// or equivalently
fn option_as_ref_original_2<T> (x_opt: &'_ Option<T>) -> Option<&'_ T>
{
    match x_opt {
        &Some(ref at_x) => Some(at_x),
        &None => None,
    }
}


fn option_as_ref_ergonomic<T> (x_opt: &'_ Option<T>) -> Option<&'_ T>
{
    // although this should not be a valid pattern match,
    // now Rust accepts it for the sake of ergonomics
    match x_opt {
        // this looks like the identity function ... but it is not!?
        Some(at_x) => Some(at_x),
        None => None,
    }
}

// proof that the above pattern is weird:
fn option_as_ref_ergonomic_fail<T> (x_opt: &'_ Option<T>) -> Option<&'_ T>
{
    match x_opt {
        // this *is* the identity function
        ret @ Some(_) => ret,
        ret @ None => ret,
    }
}

There is an implicit Deref coercion: the line is actually let _: &String = *z;

Proof with this playground example (it uses the coercion from a reference to a raw pointer, that prevents the Deref coercion from taking place):

/// Use *const to avoid Deref coercions
macro_rules! check_type {(
    $expr:expr => $T:ty
) => (
    let _: *const $T = &$expr as *const _;
)}

fn main ()
{
    let s = String::new();
    let at_s = &s;

    let _: &String = at_s;
    check_type!(at_s => &String); // OK

    let _: &str = at_s; // another form of Deref coercion
    check_type!(at_s => &str); // ERROR

    let ref z = at_s;
    let _: &String = z; // Deref coercion
    check_type!(z => &String); // ERROR
    
    let _: &String = *z;
    check_type!(*z => &String); // OK
    
    check_type!(z => &&String); // OK
}
1 Like

The ability to destructure something behind a reference without mentioning that reference, so you imply ref or ref mut, started in 1.26's match ergonomics. There was some controversy over this feature for the added complexity (which had a few unsound bugs) and that it might be more confusing. It seems to have worked out in the long run though.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.