Ownership in iterator

Hi there.
if i write

let x = String::from("abc");
let y = x

i understand why "abc" is moved from x to y. You cannot play with two variables pointing towards the same sequence. Ok,

But if i write

let r1 = 0..10;
  for x in r1
  {
    println!("{}", x);
  }

why into_iter which is moving behind it all has to take ownership of r1? Why cannot be r1 available after the loop? Ok, i can use clone etc... but i don't understand why i'm forced to do so.

thx as usual.

Ranges are a little weird, so you usually just want to clone them, but if you turn one into a Vec there's an easy fix to this kind of problem.

If you want to use the type again you need to iterate over references to the types elements. Exactly how you do that can vary from type to type, but for standard library types you can often just put an & in front of the variable you're iterating over to get a reference.

This works because Vec implements IntoIterator directly impl<T> IntoIterator for Vec<T> {...} as well as for references to a Vec impl<'a, T> IntoIterator for &'a Vec<T> {...}

Playground

  let r1 = (0..10).collect::<Vec<usize>>();
  for x in r1
  {
    println!("{}", x);
  }
  
  // fails
  //println!("{}", r1.len());
  
  let r1 = (0..10).collect::<Vec<usize>>();
  for x in &r1
  {
    println!("{}", x);
  }
  
  println!("{}", r1.len());

In this case it's an arbitrary decision meant to remove a gotcha in case you've called .next() on r1's copy. The type says it's not trivially copyable, and Rust respects this on principle.

In case of other iterators, this may be necessary for correctness. Some iterators allow you to take apart collections you're iterating. You can iterate over Vec<String> and not copy the strings, and instead destroy the Vec in the process, but get the Strings back and move them somewhere else. Such iteration has to preserve unique ownership, which means you can't have unique access to string inside and outside of the loop.

1 Like

Because you want to see that clone. Really. Compare:

  let r1 = 0..10;
  // Process the first few elements.
  for x in r1 {
    println!("{}", x);
    if … {
      break;
    }
  }
  // Process the rest.
  for x in r1 {
    println!("{}", x);
  }

with

  let r1 = 0..10;
  // Process the first few elements.
  for x in r1.clone() {
    println!("{}", x);
    if … {
      break;
    }
  }
  // Process the rest.
  for x in r1 {
    println!("{}", x);
  }

Can you spot the problem in first version the code? How about 2nd one?

Yes, if you think about first version and realize that it's, somehow, not mut object which still allows you to iterate over something… yet without internal mutability… you may realize code is wrong.

But Rust is not supposed to ask you to do such mental gymnastics! Version with explicit clone is just less confusing.

Of course clone here is very cheap and it's perfectly fine to use it with such a simple type, but … it's there to ensure that you wouldn't forget that you are dealing with copy, not original.

6 Likes

First of all you should know what an iterator exactly is. Iterator is a thing could "return" values finitely/infinitely. Every iterator has a function "next", where the value return from has a type Option<T>. When the first None was returned, this iterator is exhausted, in other word this iterator won't give values anymore, it is useless now.

Now 0..10 is a iterator, you used for to walk through it. You can choose not moving the ownership of r1, but you must give its mutable reference:

let mut r1 = 0..10;
for x in &mut r1{
  ... // No `break`s in it
}

Ok, but what's next? r1 won't return anything then. It's useless now.

for _y in &mut r1{
  unreachable!();
}

But if you propose to use break in the first loop, the second loop is reachable. The iterator will continue at where the first loop stops.


If you want

let r1 = 0..10;
for x in r1{
  ... // 0 to 10
}

let r2 = r1;
for x in r2{
  ... // Still 0 to 10
}

That is impossible, because Range<i32> doesn't implements Copy.

1 Like

Thx you all... i have to play a bit with this. My doubt, almost a philosophical question perhaps, is that I perfectly understand the risk of dropping an element to which two variables point with the risk of having a dangling pointer, while I understand less the design choice related to the iterators. But now I study around it a bit ....