Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the difference between ref and & when assigning a variable from a reference?

What is wrong with this code?

fn example() {
    let vec = vec![1, 2, 3];
    let &_y = &vec;
}
error[E0507]: cannot move out of borrowed content
 --> src/lib.rs:3:15
  |
3 |     let &_y = &vec;
  |         ---   ^^^^ cannot move out of borrowed content
  |         ||
  |         |data moved here
  |         help: consider removing the `&`: `_y`
  |
note: move occurs because `_y` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
 --> src/lib.rs:3:10
  |
3 |     let &_y = &vec;
  |          ^^

and why this is correct?

let vec = vec![1, 2, 3];
let ref _y = &vec;
like image 673
Sergey Bromov Avatar asked Jan 11 '16 08:01

Sergey Bromov


2 Answers

Pattern binding can get some using to ;)

In order to understand what the compiler does, you can use the let _: () = ...; trick. By assigning to a variable of type (), you force the compiler to print an error message giving you the type it inferred for your variable.


In the first example:

let vec = vec![1, 2, 3];
let &y = &vec;
let _: () = y;

we get:

error[E0308]: mismatched types
 --> src/lib.rs:4:13
  |
4 | let _: () = y;
  |             ^ expected (), found struct `std::vec::Vec`
  |
  = note: expected type `()`
             found type `std::vec::Vec<{integer}>`

the type of y is Vec<i32>.

What it means is that you are:

  1. Borrowing vec into a temporary
  2. Attempting to move vec into y, which is forbidden because vec is already borrowed.

The equivalent correct code would be:

let vec = vec![1, 2, 3];
let y = vec;

In the second example:

let vec = vec![1, 2, 3];
let ref y = &vec;
let _: () = y;

we get:

error[E0308]: mismatched types
 --> src/lib.rs:4:17
  |
4 |     let _: () = y;
  |                 ^ expected (), found reference
  |
  = note: expected type `()`
             found type `&&std::vec::Vec<{integer}>`

Thus y is &&Vec<i32>.

This let us see that let ref a = b; is generally equivalent to let a = &b;, and therefore in this case: let y = &&vec;.

ref is made for destructuring; for example, if you had:

let vec = Some(vec![1, 2, 3]);
if let Some(ref y) = vec {
    // use `y` here
}

you can use ref here to be able to bind y to &Vec<i32> without moving even though vec here has type Option<Vec<i32>>. Indeed, the purpose of ref is to take a reference inside an existing object during destructuring.

In general, in a let statement, you will not use ref.

And since Rust 1.26, ref is inferred in pattern matching; see the stabilization of match ergonomics.

like image 172
Matthieu M. Avatar answered Oct 08 '22 22:10

Matthieu M.


The same symbol (&) is doing two different things when used on the right-end and left-end side of a binding. The left-hand side works like a pattern matching, so:

let x = (y, z); // x contains a tuple with value (y, z)
let (a, b) = x  // x is destructured into (a, b), so now
                // a has value y and b has value z

In the same way

let x = &y; // x is a reference to y
let &z = x; // this is like let &z= &y, so we want z to be y
            // this is equivalent to let z = *x

A ref binding on the left side is saying "pattern match by reference, not by value". So these two statements are equivalent:

let ref y = vec;
let y = &vec;

although in a let, the second one is more idiomatic.

You can see more examples on the pointers/ref chapter on rust by example

like image 38
Paolo Falabella Avatar answered Oct 08 '22 22:10

Paolo Falabella