Search code examples
rustiteratorpattern-matchingrefborrow-checker

In Rust What's the diff between `&` and `ref` in the for-loop


Rust By Example says

A ref borrow on the left side of an assignment is equivalent to an & borrow on the right side.

Now:

let u = 42u32;
let x = &u;
println!("{:p}",x);
let ref y = u;
println!("{:p}", y);
// same addr printed and that's expected

let v = [1, 2, 3];
for i in &v {
    println!("{:p}", i);
    // 0x7fff4ae23334
    // 0x7fff4ae23338
    // 0x7fff4ae2333c
    // ok, still expected
}
for ref j in v {
    println!("{:p}",j);
    // SAME addr (and not 0x7fff4ae23334) printed for each round, WHY?
}

I wonder what exactly is j here in the for ref j in v loop.


Solution

  • It is true that let ref foo = bar; is equivalent to let foo = &bar;. However in for loops there is a different mechanism. In general

    for foo in bar {}
    

    uses IntoIterator trait to "convert" bar to an iterator. So when you write

    let v = vec![];
    for foo in v {}
    

    rust will use implementation impl IntoIterator for Vec, which will yield Ts. However if you write

    let v = vec![];
    for foo in &v {}
    

    rust will use implementation impl IntoIterator for &Vec, which will yield &Ts. Note that there is also implementation for &mut Vec.

    foo in for foo in bar will bind to values returned by iterator. This is because for loop is a syntactic sugar that de-sugars into this (see documentation):

    {
        let result = match IntoIterator::into_iter(bar) {
            mut iter => loop {
                let next;
                match iter.next() {
                    Some(val) => next = val,
                    None => break,
                };
                let foo = next; /* here is the binding we provided in the for loop */
                let () = { /* loop body */ };
            },
        };
        result
    }
    

    Going back to our example. for foo in &v will bind foo to references to the elements of vector. This is why they each have it's own consecutive address (note that it increases by 4 bytes in each iteration - the size of i32). But when we say for ref foo in bar ref foo will bind to the stack allocated variable into which value returned by the iterator is moved in each iteration (see next above). Since the same stack variable is used in each iteration they all will have the same addresses, however value will be changed each time.