3

Follow up this question which is marked as duplicate as question here.

The concept of how for loop implement into_iter keeps haunt me down and this answer kinda creates more question to me like the terms of re-borrow, which is not mentioned in rust official document at all except in one place.

To my understanding of this comment, when vec is a mutable reference, for i in vec.into_iter() is actually for i in (&mut *vec).into_iter() behind the scene.

How about for i in vec, is it actually i in vec.into_iter()? Is there any place that have more detail on how for loop is implemented and how re-borrowing gets triggered and how it works?

Code for reference:

fn question1_1(vec: &mut [i32]) {
    for &mut item in vec.into_iter() {
        println!("{}", item);
    }
    for &mut item in vec { // --- `vec` moved due to this implicit call to `.into_iter()`
        println!("{}", item);
    }
    vec; // Error: move occurs because `vec` has type `&mut [i32]`, which does not implement the `Copy` trait
}

pub fn main() {
    let mut s = vec![1, 2, 3];
    question1_1(&mut s);
Jack Lien
  • 105
  • 6
  • Does this answer your question? [Do mutable references have move semantics?](https://stackoverflow.com/questions/62960584/do-mutable-references-have-move-semantics) – Chayim Friedman May 16 '22 at 12:08
  • @ChayimFriedman No, I don't think so. It just explain what happened when mutable reference get passed into function. It doesn't explain how for loop works behind the scene. – Jack Lien May 16 '22 at 12:19
  • @eggyal `for _ in x` is equal to `for _ in x.into_iter()` is what thought, but they behave differently. How does `IntoIterator` make a difference? – Jack Lien May 16 '22 at 12:26

2 Answers2

1

for loop is desugared into (this can be seen by inspecting the HIR):

{
    let _t = match IntoIterator::into_iter(iter) {
        mut iter => loop {
            match Iterator::next(&mut iter) {
                None => break,
                Some(i) => {
                    // Body
                }
            }
        },
    };
    _t
}

Specifically, the iterable is transformed into an iterator by executing IntoIterator::into_iter(iterable). Each trait, including IntoIterator, has a hidden Self generic parameter, and so this is actually IntoIterator::<_>::into_iter(iterable). From reborrowing POV, this is similar to:

fn foo<T>(v: T) {}

foo::<_>(iterable)

The exact details are explained in Do mutable references have move semantics?, but the general idea is, it is not documented. And the current working is such that when the compiler cannot determine without inference that something is a mutable reference, it is not reborrowed. Since _ requires inference, it is not reborrowed.

The receiver works differently (always reborrowed), and thus iterable.into_iter() does perform reborrowing. In fact, this behavior is related to autoref and not reborrowing. See also What are Rust's exact auto-dereferencing rules?.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • What do you mean by HIR, POV, and receiver? – Jack Lien May 16 '22 at 12:33
  • 2
    [HIR](https://rustc-dev-guide.rust-lang.org/hir.html) - High-level Intermediate Representation. It is some form of desugaring rustc uses during the compilation process. You can inspect it for a given program at the playground by clicking the menu (three dots) -> HIR. POV - Point Of View. Receiver - the object you put the dot on, i.e. `receiver.method_name(argument1, argument2, ...)`. – Chayim Friedman May 16 '22 at 12:37
  • Just to make sure, receiver is also using `IntoIterator::into_iter,` same as for loop but for loop passed iterable as a parameter to `IntoIterator::into_iter` am I right? – Jack Lien May 16 '22 at 12:52
  • 1
    When you do `iter.into_iter()` explicitly, sure this gets passed into `IntoIterator::into_iter()` by `for` using [the blanket `impl IntoIter for I`](https://doc.rust-lang.org/1.60.0/src/core/iter/traits/collect.rs.html#258-266), but the reborrowing is already performed by the explicit call. – Chayim Friedman May 16 '22 at 12:55
0

Besides using HIR on playground to inspect how for loop run behind the scene, Here is the official documentation on how for loop is de-sugared.

From the example given by the document, values (a vec type) was passed into IntoIterator::into_iter() IntoIterator::into_iter(values), which is different than calling as a method values.into_iter().

let values = vec![1, 2, 3, 4, 5];

for x in values {
    println!("{}", x);
}

De-sugared into

let values = vec![1, 2, 3, 4, 5];
{
    let result = match IntoIterator::into_iter(values) {
        mut iter => loop {
            let next;
            match iter.next() {
                Some(val) => next = val,
                None => break,
            };
            let x = next;
            let () = { println!("{}", x); };
        },
    };
    result
}
Jack Lien
  • 105
  • 6