pub fn event_handler(state: &State, event: Event) -> State
...which feels more natural... Thing is, the above can't be done as I can't copy State to return it if it hasn't changed. I also want to avoid passing the existing State in by value.
The event_handler conditionally yields a new State. Looking at the Cow type seems as though it may be the go... but it isn't available for nostd which is my world.
Thanks. By feeling "more natural", I probably mean, "what I'm used to!". In the JVM world, I'd pass in a state object reference and have returned a state object reference. Thus, the second signature I provided looks more like what I'd expect. That said, what I expect doesn't mean idiomatic Rust at this stage (I'm a newbie to Rust).
let mut new_state = prev_state;
for event in events.iter() {
new_state = event_handler(&new_state, *event).into_owned();
}
In my particular use-case, the events supplied will have a prev_state that's valid and cause Cow::Owned types to be returned i.e. into_owned should never cause a clone to occur in reality.
Thanks for the feedback so far. Happy to take further advice being the newbie that I am.
"Reference" in the JVM world (or, more generally, in most garbage-collected languages) is more like Rc<RefCell<T>> in Rust (or Arc<Mutex<T>>, is they are thread-safe), since they are a kind of "shared-mutable-owned". You can use this everywhere, if you like, but I'm afraid this will quickly make your code very hard to understand.
I think this is a good opportunity to look at what you're doing here from an ownership perspective.
You're passing a borrow of new_state to the event_handler, but then you just don't use it any more. So from how it's used, it seems like it'd be perfectly reasonable to pass it in owned. Then there's something inherently nice about
let mut new_state = prev_state;
for event in events.iter() {
new_state = event_handler(new_state, *event);
}
That means there's definitely never a clone needed, and if there's any complex data in a state that doesn't need to be cloned internally (like the observations looks like it is).
(EDIT: could maybe even for event in events, and the *event could just be event. Dunno if you need the events elsewhere too.)
So the implementation might be more like this:
pub fn event_handler(state: State, event: Event) -> State { // EDIT: Oops, forgot to remove the `Cow` here
match state {
State::Idle { observations } => match event {
Event::EntryExitsEmitted => state,
Event::ExpectingInsideMovement => State::PendingInsideMovement {
observations: observations,
},
...
},
...
}
Which seems shorter and more efficient.
So basically the solution to not being Copy is to just pass in something owned instead
Thanks. I've now created something a little more abstract in the playground. Hopefully, this helps: Rust Playground. In particular, I don't understand how a Cow<State> can be returned in your example.
Also, I feel that I should be passing the state in by reference and not by value, as it is essentially a struct that may evolve over time to include more fields.
Thanks to @scottmcm, I've now re-thought ownership and conclude that I was overthinking the costs of Rust's move behaviour and wanting to pass references instead. In actuality, the moves will likely become optimised out by LLVM and, even if not, the moves are small. This is the final signature I arrived at:
pub fn event_handler(state: State, event: Event) -> State
Thanks again. I like to specify each match branch explicitly when writing state machine handlers as it then makes me stop and think a little more about what happens in each situation. It often pays off.