3

I have seen std::mem::swap and std::mem::replace, I'm looking for something similar to replace a memory location with a new value computed based on consuming the old value. I would like to take the value (like with std::mem::take) but not leave a Default in it's place, but replace it immediately with a new value created based on the value I took.

I have written a rough example:

struct StateA {
    s: String
}

struct StateB {
    i: usize
}

enum State {
    A(StateA),
    B(StateB)
}

struct Doer {
    state: State
}

impl Doer {
    pub fn do_thing(&mut self) {
        // Here I have a problem, code like this doesn't exist!
        std::mem::update(&mut self.state, move |state| {
            match state {
                State::A(state_a) => State::B(do_a_thing(state_a)),
                State::B(state_b) => State::A(do_b_thing(state_b))
            }
        })
        
    }
}

// These functions should not be modified, I'd like to assume that they are from external dependencies

fn do_a_thing(a: StateA) -> StateB {
    StateB {
        i: a.s.len()
    }
}

fn do_b_thing(b: StateB) -> StateA {
    StateA {
        s: b.i.to_string()
    }
}

(Here is a Rust Playground link but the code doesn't compile, obviously)

I could make my state Optional, I could also add another enum variant as an intermediate Default value. Is there a way without doing those things?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
quantumbyte
  • 503
  • 1
  • 6
  • 13
  • 2
    Take a look at [`replace_with`](https://crates.io/crates/replace_with), whose motivational example looks almost exactly like what you're doing. – user4815162342 Jan 29 '23 at 21:36
  • Ha! Indeed! Event the State enum is the same. That looks exactly like what I need, thanks for the hint! – quantumbyte Jan 29 '23 at 21:42
  • After reading into this, as well as the RFC, I understand why it doesn't exist: Temporarily taking ownership of the `State` isn't good, and if the closure panics, it's impossible to recover a value to put back. – quantumbyte Jan 29 '23 at 22:24

1 Answers1

4

The functionality I'm looking for doesn't exist, because it requires to temporarily take ownership from a &mut T. The RFC 1736 contains extensive discussion about this.

There is the replace_with crate which gives exactly the function that I wanted, but given the discussion in the RFC I opted against this approach altogether.

Since I can change the Enum that I'm working with, I have chosen the additional enum variant approach:

enum State {
    A(StateA),
    B(StateB),
    Intermediate
}

...

impl Doer {
    pub fn do_thing(&mut self) {
        let s = std::mem::replace(&mut self.state, State::Intermediate);
        std::mem::replace(&mut self.state, match s {
            State::A(state_a) => State::B(do_a_thing(state_a)),
            State::B(state_b) => State::A(do_b_thing(state_b)),
            State::Intermediate => panic!()
        });
    }
}
quantumbyte
  • 503
  • 1
  • 6
  • 13