Minimal code equivalent to for loop; deref conversion and for loops

Hello,

According to the Rust Reference a for loop

'label: for PATTERN in iter_expr {
    /* loop body */
}

is equivalent to

{
    let result = match IntoIterator::into_iter(iter_expr) {
        mut iter => 'label: loop {
            let mut next;
            match Iterator::next(&mut iter) {
                Option::Some(val) => next = val,
                Option::None => break,
            };
            let PATTERN = next;
            let () = { /* loop body */ };
        },
    };
    result
}

Why is the single-arm match expression above needed? Isn't the for loop already equivalent to the following shorter template?

{
    let mut iter = IntoIterator::into_iter(iter_expr);
    'label: loop {
        let mut next;
        match Iterator::next(&mut iter) {
            Option::Some(val) => next = val,
            Option::None => break,
        };
        let text = next;
        let () = { /* loop body */ };
    }
}

Actually, I got into the above while trying to understand why a for loop over v.iter() and &v seems to behave the same (v is some Vec<T> or &Vec<T>). The answer seems to be that this is because the stdlib implements the IntoIterator trait for references to vectors. In other words, a user-defined collection type would not necessarily behave in the same way.

Now that it is clear that for iterates over the result of IntoIterator::into_iter(iter_expr), shouldn't deref conversion apply? For example, when v is a Box<Vec<T>>, v.iter() will work, but &v won't (but &*v will...). Concretely, in the following working program &*v may be replaced by v.ter() but replacing it by &v is an error.

fn main() {
    let v = ["apples", "cake", "coffee"];
    let v = v.map(|s| String::from(s));
    let v: Box<[String]> = Box::from(v);

    for text in &*v {
        println!("I like {}.", text);
    }
}

But shouldn't &v be allowed because of deref conversion? In that RFC the following example is given:

fn use_nested(t: &Box<T>) {
    use_ref(&**t);  // what you have to write today
    use_ref(t);     // what you'd be able to write (note: recursive deref)
}

In the above, substitute &v for t to seemingly arrive at the same situation as my example.

Possible solution: In the following variant, deref conversion does indeed work:

fn my_iter(v: &[String]) -> std::slice::Iter<String> {
    IntoIterator::into_iter(v)
}

fn main() {
    let v = ["apples", "cake", "coffee"];
    let v = v.map(|s| String::from(s));
    let v: Box<[String]> = Box::from(v);

    for text in my_iter(&v) {
        println!("I like {}.", text);
    }
}

So it seems that calling a trait function like IntoIterator::into_iter does not quite behave in the same way as calling a simple function like my_iter. I would be interested to better understand the background of this.

Temporary lifetimes have very specific rules, which really matter for Dropping things like mutex guards, and thus some of the desugars use matches like this for the temporary behaviour.

The point of the reference is to be precise in all the details. But as a normal human, you can basically always think of

for PATTERN in INTOITER { BODY }

as

let mut it = INTOITER.into_iter();
while let Some(PATTERN) = it.next() {
    BODY
}

and not worry about all the extra stuff.

1 Like

Deref coercion requires a known "destination" type. When you write Trait::trait_method(...) the compiler has to make some assumptions about what type is implementing the trait, and that gets in the way of a deref coercion here.

So this doesn't work

let v = ["apples", "cake", "coffee"];
let v = v.map(String::from);
let v: Box<[String]> = Box::from(v);

let iter = IntoIterator::into_iter(&v);

But this does

Playground

let v = ["apples", "cake", "coffee"];
let v = v.map(String::from);
let v: Box<[String]> = Box::from(v);

let iter = <&[String] as IntoIterator>::into_iter(&v);

When the compiler knows the destination type it realizes it can do a deref coercion.

Many thanks @scottmcm and @semicoleon!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.