0

Trying to understand how & works in a rust for..in loop... For example let's say we have something simple like a find largest value function which takes a slice of i32's and returns the largest value.

fn largest(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for item in list {
        if *item > largest {
            largest = *item;
        }
    }

    largest
}

In the scenario given above item will be an &i32 which makes sense to me. We borrow a slice of i32's and as a result the item would also be a reference to the individual item in the slice. At this point we can dereference the value of item with * which is what I assume how a pointer based language would work.

But now if we alter this slightly below...

fn largest(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

If we put an & in front of item this changes item within the for..in into an i32... Why? In my mind this is completely counterintuitive to how I would have imagined it to work. This to me says, "Give me an address/reference to item"... Which in itself would already be a reference. So then how does item get dereferenced? Is this just a quirk with rust or am I fundamentally missing something here.

James Chung
  • 11
  • 1
  • 3

2 Answers2

7

All variable assignments in Rust, including loop variables in for loops and function arguments, are assigned using pattern matching. The value that is being assigned is matched against the target pattern, and Rust tries to fill in the "blanks", i.e. the target variable names, in a way that substituting the values makes the pattern match the value. Let's look at a few examples.

let x = 5;

This is the simplest case. Obvious, substituting x with 5 makes both sides match.

if let Some(x) = Some(5) {}

Here, x will also become 5, since substituting that value into the pattern will make both side identical.

let &x = &5;

Again, the two sides match when setting x to 5.

if let (Some(&x), &Some(y)) = (Some(&5), &Some(6)) {}

This assignment results in x = 5 and y = 6, since substituting these values into the pattern makes both sides match.

Let's apply this to your for loop. In each loop iteration, the pattern after for is matched against the next value returned by the iterator. We are iterating an &[i32], and the item type of the resulting iterator is &i32, so the iterator yields a &i32 in each iteration. This reference is matched against the pattern &item. Applying what we have seen above, this means item becomes an i32.

Note that assigning a value of a type that does not have the Copy marker trait will move that value into the new variable. All examples above use integers, which are Copy, so the value is copied instead.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
1

There is no magic here, pure logic. Consider this example:

let a = 1;
let b = &a;  // b is a reference to a
let &c = &a; // c is a copy of value a

You can read the third line of the example above as "Assign reference to a to a reference to c". This basically creates a virtual variable "reference to c", assigns to it the value &a and then dereferences it to get the value of c.

let a = 1;
let ref_c = &a;
let c = *ref_c;

// If you try to go backwards into this assignments, you get:
let &c = &a;
let &(*ref_c) = &a;
let ref_c = &a; // which is exactly what it was

The same occurs with the for .. in syntax. You iterate over item_ref, but assign them to &item, which means that the type of item is Item.

for item_ref in list {
  let item = *item_ref;
  ...
}

// we see that item_ref == &item, so above is same as
for &item in list {
  ...
}
Maxim Gritsenko
  • 2,396
  • 11
  • 25