What's going on under the hood of a for i in array?

There's the code

let array = [1,2,3];
for i in array {
}

Are the array values ​​copied to i, like c++ for(auto i : array)?
Or does Rust make an iterable sequence from an array and i as an iterator go through the sequence without copying?

The for in construct is able to interact with an Iterator in several ways. As discussed in the section on the Iterator trait, by default the for loop will apply the into_iter function to the collection.

https://doc.rust-lang.org/rust-by-example/flow_control/for.html#for-and-iterators

1 Like

But technically it does copy, because the IntoIterator moves the array (unless optimized out, which in my experience compiler has trouble with).

IntoIterator moves the array, and then also every element is moved into i.

(At least notionally, i.e. modulo optimizations.)

2 Likes

Moves ? Then Why does it work?

let array = [1,2,3];
for i in array {
}
for i in array {
}

Moves or copies, depending on the semantics of the type.

Because [i32; _] is Copy.

Yeah, but the elements moves are usually not a problem. The array move can be (speaking of experience).

1 Like

I know why, because array stored on stack

No? It's a property of the type, no matter where the value is stored.

1 Like

Copies, but not as expensive as in c++ for (auto i : array) or the same?

Well, it depends. If the C++ has expensive copy constructor, then Rust's are cheaper, because it has no copy constructors. If in C++ it's a simple memcpy, in Rust it's too.

1 Like

Thanks!

If you're worried about copying, then you want for i in &array, which iterates the slice, not the array (giving you references, rather than owned items).

3 Likes

I'll leave this here

If a collection type C provides iter(), it usually also implements IntoIterator for &C, with an implementation that just calls iter(). Likewise, a collection C that provides iter_mut() generally implements IntoIterator for &mut C by delegating to iter_mut(). This enables a convenient shorthand:

let mut values = vec![41];
for x in &mut values { // same as `values.iter_mut()`
    *x += 1;
}
for x in &values { // same as `values.iter()`
    assert_eq!(*x, 42);
}
assert_eq!(values.len(), 1);

While many collections offer iter(), not all offer iter_mut(). For example, mutating the keys of a HashSet<T> could put the collection into an inconsistent state if the key hashes change, so this collection only offers iter().

Isn't it the other way around?

They were quoting the doc. :slight_smile:

Ah wow I didn't know that. A little source code example from the std for fun

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a, T, A: Allocator> IntoIterator for &'a Vec<T, A> {
    type Item = &'a T;
    type IntoIter = slice::Iter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

Rust has destructive moves. You don't have to worry about invalidating a value. If the array is not Copy, then you won't be able to use it after it was moved out from. The compiler catches this sort of mistake along with shared mutability (eg. iterator invalidation). That is the point of the ownership and borrowing system.

memcpy() is still emitted, and that might be a performance problem.

1 Like