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?
self.step
" – wrong, a mutable reference still always has to point to a value valid for that type, that's why you can't movestep
. Consider what happens when between moving and replacing it there is apanic!
, thenself.step
would be uninitialized (still pointing to the old data) and both the caller and the function itself would try to free it. – Disaccreditstd::mem::replace()
, unlike the duplicate where it's not possible because the enum has no trivially constructible variant. While some of the answers in the other question do mentionreplace()
, they only do it in passing, since it doesn't apply to that question. The answer by the OP there even advocates replacing withmem::uninitialized()
, which is just UB. – Underbody