Understanding how references work

I'm new to Rust, looking for some help in understanding how references work.

In the following code, the type of variable: item in the for loop is &i32, because sn is a slice (reference). I understand this.

    let n: Vec<i32> = vec![1];
    let sn: &[i32] = &n;
    for item in sn {
        println!("{}", *item); // ok, item can be dereferenced because it's a reference.
    }

But if I change item to &item as below, the type of item becomes i32. How did this happen?
I was expecting the the type of item to be &&i32.

    for &item in sn {
        println!("{}", *item); // error: type `i32` cannot be dereferenced
    }

rustc 1.60.0 (7737e0b5c 2022-04-04)

the general syntax for a for-loop is for pattern in expr { ... } where expr is the value we are iterating over and pattern is a "pattern" which will be used to bind variables to the next item.

The simplest pattern just binds a variable to something (e.g. for i in 0..100 { ... } or let x = 42), but you can also use pattern matching to destructure a tuple (e.g. let (head, tail) = slice.split_at(5)) or move out of a reference. That means in for &x in sn { ... } and let &x = &sn[4], the x variable will be a i32 because we've "peeled away" the reference in the original &i32 to get a copy of the value it references (the compiler uses the phrase, "moving out of a reference").

Note that you can only move out of a reference with Copy types. That's because if you tried to move value out of a reference to a non-trival type (e.g. &String) we'd have the awkward situation where the original String is pointing to data that it no longer owns (because we just passed ownership to the new variable). C++ solves this using "move constructors" - special constructors which are invoked implicitly to "patch up" the &String we moved out of - but Rust deliberately chose not to go down that path and instead you need to write explicit code to do that patching up (e.g. call .clone() to get a copy or use std::mem::take() to swap the old value out with a default one).

Don't worry if that last paragraph made no sense because you'll pick it up in due time. I'm mainly talking about the "Unable to move out of x because String doesn't implement Copy" error you might see when trying this sort of pattern matching. I'm also simplifying a bit here, but hopefully some of that will make sense.

4 Likes

Michael's explanation is great so I'd just like to share how I "read" something like

    for &item in sn {
        println!("{}", *item); // error: type `i32` cannot be dereferenced
    }

I would parse this as "for references to item in sn, iterate the items", so instead of reading it as adding another reference as you may have first thought, it's more like acknowledging that sn contains references and saying that we would like to iterate over the values that are referenced instead of the references themselves.

2 Likes

See also my blog post on the issue.

4 Likes

Analogy to other kinds of patterns might help making this more intuitive:


Well, actually, as a first (preliminary) step it’s important to realize that

for &item in sn {
    println!("{}", *item);
}

stands for something like

for the_item in sn {
    let &item = the_item;
    println!("{}", *item);
}

and not for something like

for the_item in sn {
    let item = &the_item;
    println!("{}", *item);
}

Now we can talk about why it makes sense that let &item = the_item; will remove one layer of reference, not add it; by comparing to similar expressions with tuples and arrays:

The most familiar/straightforward kind of pattern matching in let for many people is probably something like

let tuple = (1, 2);
let (a, b) = tuple;

Here, the “pattern” (a, b) in a letsplits up the tuple back into its two parts. The same thing works also for arrays:

let array = [1, 2];
let [a, b] = array; // a and b are `i32`s again

There is also the option to have an array with only a single element[1]

let array = [1];
let [a] = array; // a is an `i32` again

and with this example, we’re already very close to how things work with references, too. Both single-element arrays and references only contain a single element. The [1] expression (and the right-hand-side of the “=” in a let is always an expression) places the 1 into an array, and the [a] pattern (and the left-hand-side of the “=” in a let is always a pattern) takes the value back out of the array. And analogously, &1 in an expression vs. &a in a pattern puts either takes a reference to the value, or retrieves the value from behind a reference, respectively (the latter is also known as dereferencing).

let reference = &1;
let &a = reference; // a is an `i32` again

So in effect,

let &a = reference;

dereferences, so on a reference: &i32, it does the same as

let a = *reference;

  1. the same thing is true for tuples, but their syntax is probably a bit more confusing, so let’s go with arrays instead ↩︎

7 Likes

Offtopic on:

Those are not analogies, those are similes.

Offtopic off. :sweat_smile:

3 Likes

Thanks everybody for your replies :grinning: