I'm writing a state machine which relies on the fact that it isn't Clone
for memory safety (as it is a proxy for operations that are not data-race safe). In addition, it is using the nb crate to support non-blocking operation (without programming myself into requiring nonblocking operation, as is the goal of nb). This means that I need to provide a polling function that can be called over and over again and directly returns a result of type nb::Result
.
Note that this is targeting an ARM Cortex-M microcontroller and I am using no_std
.
For this question, here is a contrived example
pub struct MainState {
_0: ()
}
impl MainState {
fn try_advance(&mut self) -> nb::Result<AdvanceResult, MyError> {
// although this method takes a mut self, it is actually an
// idempotent method. Calling it over and over does not actually
// mutate self. The mut self is to enforce that the unsafe memory
// memory operations that happen here are free from races.
// ... do unsafe stuff ...
}
}
pub enum AdvanceResult {
Advance(AdvanceToken),
Done,
}
pub struct AdvanceToken {
fn advance(self, state: MainState) -> IntermediateState {
...
}
}
pub struct IntermediateState {
_0: ()
}
impl IntermediateState {
fn do_a_thing(self, data: u8) -> MainState {
...
}
}
My specific problem lies in the AdvanceToken::advance
method requiring that the passed StateMachine be consumed. Let's say I want to wrap this state machine in a convenient struct (perhaps to facilitate a borrow of some data):
pub struct Convenience<'a> {
state: MainState
data: &'a [u8],
index: u8
}
impl<'a> Convenience<'a> {
pub fn try_finish(&mut self) -> nb::Result<(), MyError> {
match self.state.try_advance() {
//...blah blah handle Err(nb::Result::WouldBlock) and other stuff..
Ok(result) => match result {
AdvanceResult::Advance(t) => {
self.state = t.advance(self.state).do_a_thing(self.data[self.index]);
self.index += 1;
Err(nb::Result::WouldBlock)
}
AdvanceResult::Done => Ok(())
}
}
}
}
We now have a problem. The self.state = t.advance(self.state).do_a_thing(self.data[self.index]));
line causes the borrow checker to complain. It rightfully states that I am "moving self.state out of borrowed content". So yes, I move it out, but I put it back later. What I basically need to be able to do is consume a member of a struct, promising the borrow checker that I will replace it with another value (without actually using mem::replace
, since I don't have anything to replace it with until I consume it), and then replace it with the new value.
Is there any way to solve this problem? Or should I reconsider how I am doing this? I keep running into this over and over again when I try to build state machines that use a type to represent each state, since I tend to keep those state machine instances in structs elsewhere.