12

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;
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sergey Bromov
  • 123
  • 1
  • 6

3 Answers3

18

Pattern binding can take some getting used 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.

hippietrail
  • 15,848
  • 18
  • 99
  • 158
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Is there a legit case where we want to use something like `let &y = &vec;`? – WiSaGaN Jan 11 '16 at 09:54
  • @WiSaGaN: I cannot think of any. – Matthieu M. Jan 11 '16 at 11:02
  • @WiSaGaN: This construction may be used for example in closures. It's used in function retain in Vec and so on. – Sergey Bromov Jan 11 '16 at 11:49
  • 1
    No-one has said it directly, but it seems worth mentioning for completeness: the `let ref y = &vec;` example is equivalent to `let y = &&vec;`. – huon Jan 11 '16 at 13:31
  • @huon: Good call, I added a little blurb to that effect after showing off the type of `y` in the second case. – Matthieu M. Jan 11 '16 at 13:38
  • @SergeyBromov: I could not find such construct [here](https://github.com/rust-lang/rust/blob/master/src/libcollections/vec.rs#L611), do you have an up to date example? I'd really like the understand the usecase. – Matthieu M. Jan 11 '16 at 13:42
  • Usually `&`-pattern is not idiomatic - often it is possible to use dereference operator `*`. For example, if a method on an enum accepts `&self`, it is better to write `match *self { Variant1(...) => ... }` instead of `match self { &Variant1(...) => ... }`. However, sometimes it can be used for much clearer code, for example, when you need to process an iterator of tuples with `filter()`: `vec.enumerate().filter(|&(i, ref v)| ...)`. I don't think I've ever seen `&`-pattern in `let` bindings. – Vladimir Matveev Jan 11 '16 at 13:55
  • @MatthieuM.: in func. 'retain' I mean that when we pass argument &v[i] to closure |&x| x%2 == 0 we get the simple i32; |x| *x%2 == 0 is equivalent, thus all is OK; in other side if we passed Vec to closure |&x | we get error 'cannot move out of borrowed content', thus |x| is correct, that's why I asked my question. – Sergey Bromov Jan 12 '16 at 05:45
  • @SergeyBromov: Ah! I understand now. Yes indeed pattern matching also applies in closure/functions arguments. – Matthieu M. Jan 12 '16 at 07:14
  • Note that `ref` is not needed for destructuring anymore. `Some(ref y) = vec` can be written as `Some(y) = &vec` now. – Chronial Jan 22 '19 at 14:40
  • @Chronial: Hum... match ergonomics, right? I haven't tested with "deep" scenarios, do you know if it would work then too? – Matthieu M. Jan 22 '19 at 15:11
  • What do you mean with "deep"? This will work also work with arbitrary many levels of nesting. – Chronial Jan 22 '19 at 22:43
  • @Chronial: Arbitrary many levels is what I was wondering. It would have surprised me if it didn't, but just in case... thanks for the confirmation. – Matthieu M. Jan 23 '19 at 07:58
5

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

Paolo Falabella
  • 24,914
  • 3
  • 72
  • 86
1

First of all, you do not need the & in your working example. If you use it, you end up with a &&Vec<_>, which you don't really need.

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

The problem with your first code is that you are doing two things at once. Lets split it up into two:

The first part creates a reference to vec

let y1 = &vec;

The second part (the & before the variable binding), destructures.

let &y2 = y1;

This means you are trying to move out of the reference, which only works if the type is Copy, because then any attempt to move will copy the object instead.

oli_obk
  • 28,729
  • 6
  • 82
  • 98