I'd like to make the following Padded
type into an iterator transformer:
enum Step<T> {
Before(T),
During(T),
After
}
struct Padded<T> {
step: Step<T>
}
(Note that in my real code, there's stuff other than just Step
inside Padded
, hence the extra layer of indirection from struct
to enum
.)
The idea is that in each iteration, we always change our Step
and so it should be kosher to move the T
stored in it into the next Step
's constructor. However, I can't get it to work.
Straightforward version:
impl<T: Iterator> Iterator for Padded<T> {
type Item = Option<T::Item>;
fn next(&mut self) -> Option<Self::Item> {
match &self.step {
Step::Before(start) => {
self.step = Step::During(*start);
Some(None)
},
Step::During(ref mut iter) => {
match iter.next() {
Some(x) => { self.step = Step::During(*iter); Some(Some(x)) },
None => { self.step = Step::After; Some(None) },
}
},
Step::After => {
None
}
}
}
}
This fails with:
error[E0507]: cannot move out of `*start` which is behind a shared reference
--> src/lcd.rs:39:42
|
39 | self.step = Step::During(*start);
| ^^^^^^ move occurs because `*start` has type `T`, which does not implement the `Copy` trait
error[E0596]: cannot borrow data in a `&` reference as mutable
--> src/lcd.rs:42:26
|
42 | Step::During(ref mut iter) => {
| ^^^^^^^^^^^^ cannot borrow as mutable
error[E0507]: cannot move out of `*iter` which is behind a mutable reference
--> src/lcd.rs:44:59
|
44 | Some(x) => { self.step = Step::During(*iter); Some(Some(x)) },
| ^^^^^ move occurs because `*iter` has type `T`, which does not implement the `Copy` trait
I tried making it a bit more obvious that self.step
is not long for this world anyway, by changing the third branch to:
Step::After => {
self.step = Step::After;
None
}
but that doesn't change anything.
Then I thought I'd spell it out even more explicitly what is going on here:
impl<T: Iterator> Padded<T> {
fn next_self_and_item(self) -> (Self, Option<Option<T::Item>>) {
match self.step {
Step::Before(start) => {
(Padded{ step: Step::During(start) }, Some(None))
},
Step::During(mut iter) => {
match iter.next() {
Some(x) => (Padded{ step: Step::During(iter) }, Some(Some(x))),
None => (Padded{ step: Step::After }, Some(None)),
}
},
Step::After => {
(Padded{ step: Step::After }, None)
}
}
}
}
This one does pass the borrow checker, but can't be used to implement Iterator::next
:
impl<T: Iterator> Iterator for Padded<T> {
type Item = Option<T::Item>;
fn next(&mut self) -> Option<Self::Item> {
let (new_self, item) = self.next_self_and_item();
*self = new_self;
item
}
}
error[E0507]: cannot move out of `*self` which is behind a mutable reference
--> src/lcd.rs:79:32
|
79 | let (new_self, item) = self.next_self_and_item();
| ^^^^ -------------------- `*self` moved due to this method call
| |
| move occurs because `*self` has type `Padded<T>`, which does not implement the `Copy` trait
|
note: `Padded::<T>::next_self_and_item` takes ownership of the receiver `self`, which moves `*self`
--> src/lcd.rs:57:27
|
57 | fn next_self_and_item(self) -> (Self, Option<Option<T::Item>>) {
| ^^^^
So what can I do? Also, am I right in thinking that at least morally, what I'm trying to do should be fine, since self
is a mutable reference (so I should be able to do whatever I want to self.step
), and in all branches the T
inside self.step
is merely moved around?
As pointed out in the comments, it is not allowed to move a non-Copy
value out of a reference without caring what remains there. This is because a Rust reference is owned by someone else and is expected to always refer to valid data. If the compiler allowed such extraction, then a panic (or just an early return) would leave the owner with a destructed value, leading to a crash or worse.
However, if you can supply a valid alternative value, std::mem::replace()
allows you to extract the value from the reference while leaving the specified replacement in its place. Since your Step
enum has a variant that doesn't contain a T
, you can easily retrieve your "hot potato" by (temporarily) replacing self.step
with Step::After
:
fn next(&mut self) -> Option<Self::Item> {
// take ownership of self.step by replacing it with Step::After
match std::mem::replace(&mut self.step, Step::After) {
Step::Before(start) => {
self.step = Step::During(start);
Some(None)
}
Step::During(mut iter) => match iter.next() {
Some(x) => {
self.step = Step::During(iter);
Some(Some(x))
}
None => Some(None), // we've already changed it to Step::After
},
Step::After => None,
}
}
std::mem::replace()
is allowed to move value out of a mut reference because it guarantees that no arbitrary code (which could panic) runs before the new value is swapped in.