Here's a valid Rust snippet:
fn main() {
let mut range = 0..5;
for num in &mut range {
if num == 2 {
break;
}
}
for num in range {
println!("remaining {num}");
}
}
It prints:
remaining 3
remaining 4
Notably, if I remove the &mut
from the third line, it doesn't compile
(error[E0382]: use of moved value: `range`
), because Range
is not
Copy
. However, if we implement our own CustomRange
iterator that is
Copy
, we can provoke some weird behavior. First lets keep the code mostly the
same and change only the iterator:
#[derive(Clone, Copy)]
struct CustomRange {
i: u64,
n: u64,
}
impl Iterator for CustomRange {
type Item = u64;
fn next(&mut self) -> Option<u64> {
if self.i < self.n {
self.i += 1;
Some(self.i - 1)
} else {
None
}
}
}
fn main() {
let mut range = CustomRange { i: 0, n: 5 };
for num in &mut range {
if num == 2 {
break;
}
}
for num in range {
println!("remaining {num}");
}
}
remaining 3
remaining 4
Nothing out of the ordinary so far. But the problem is that the &mut
above is
no longer required. If we remove it and make the main function look like this:
fn main() {
let range = CustomRange { i: 0, n: 5 };
for num in range {
if num == 2 {
break;
}
}
for num in range {
println!("remaining {num}");
}
}
Now we see the problem:
remaining 0
remaining 1
remaining 2
remaining 3
remaining 4
The behavior is easy to explain once you're staring at it. The
IntoIterator::into_iter
call implicit in each for
loop takes self
by
value, which for Copy
types means we make a copy of them, so the first for
loop does not mutate our copy of iter
. The compiler kinda sorta hints at
this if you keep let mut range
in the code, because it'll tell you that the
mut
there is doing nothing. But if you just write let range
without
thinking about it, the code has very surprising behavior with no warnings.
The rule that most Rustaceans agree on is that anything that implements
Iterator
should not implement Copy
, to make it harder for confusing code
like this to compile. In most cases where callers really want to copy an
iterator, they can call .clone()
explicitly.