How to move an opaque value like a hot potato from one Enum constructor to the next to the next?
Asked Answered
T

1

6

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?

Taciturnity answered 8/1 at 13:56 Comment(2)
" I should be able to do whatever I want to self.step" – wrong, a mutable reference still always has to point to a value valid for that type, that's why you can't move step. Consider what happens when between moving and replacing it there is a panic!, then self.step would be uninitialized (still pointing to the old data) and both the caller and the function itself would try to free it.Disaccredit
I reopened this question because it seemed a clear candidate for std::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 mention replace(), they only do it in passing, since it doesn't apply to that question. The answer by the OP there even advocates replacing with mem::uninitialized(), which is just UB.Underbody
U
6

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,
    }
}

Playground

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.

Underbody answered 8/1 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.