Рассмотрим следующий пример кода ( детская площадка ).
#[derive(PartialEq, Clone, Debug)]
enum State {
Initial,
One,
Two,
}
enum Event {
ButtonOne,
ButtonTwo,
}
struct StateMachine {
state: State,
}
impl StateMachine {
fn new() -> StateMachine {
StateMachine {
state: State::Initial,
}
}
fn advance_for_event(&mut self, event: Event) {
// grab a local copy of the current state
let start_state = self.state.clone();
// determine the next state required
let end_state = match (start_state, event) {
// starting with initial
(State::Initial, Event::ButtonOne) => State::One,
(State::Initial, Event::ButtonTwo) => State::Two,
// starting with one
(State::One, Event::ButtonOne) => State::Initial,
(State::One, Event::ButtonTwo) => State::Two,
// starting with two
(State::Two, Event::ButtonOne) => State::One,
(State::Two, Event::ButtonTwo) => State::Initial,
};
self.transition(end_state);
}
fn transition(&mut self, end_state: State) {
// update the state machine
let start_state = self.state.clone();
self.state = end_state.clone();
// handle actions on entry (or exit) of states
match (start_state, end_state) {
// transitions out of initial state
(State::Initial, State::One) => {}
(State::Initial, State::Two) => {}
// transitions out of one state
(State::One, State::Initial) => {}
(State::One, State::Two) => {}
// transitions out of two state
(State::Two, State::Initial) => {}
(State::Two, State::One) => {}
// identity states (no transition)
(ref x, ref y) if x == y => {}
// ^^^ above branch doesn't match, so this is required
// _ => {},
}
}
}
fn main() {
let mut sm = StateMachine::new();
sm.advance_for_event(Event::ButtonOne);
assert_eq!(sm.state, State::One);
sm.advance_for_event(Event::ButtonOne);
assert_eq!(sm.state, State::Initial);
sm.advance_for_event(Event::ButtonTwo);
assert_eq!(sm.state, State::Two);
sm.advance_for_event(Event::ButtonTwo);
assert_eq!(sm.state, State::Initial);
}
В методе StateMachine::transition
приведенный код не компилируется:
error[E0004]: non-exhaustive patterns: `(Initial, Initial)` not covered
--> src/main.rs:52:15
|
52 | match (start_state, end_state) {
| ^^^^^^^^^^^^^^^^^^^^^^^^ pattern `(Initial, Initial)` not covered
Но это именно тот шаблон, которому я пытаюсь соответствовать! Вместе с ребром (One, One)
и ребром (Two, Two)
. Важно, что я особенно хочу этот случай, потому что я хочу использовать компилятор, чтобы гарантировать, что каждый возможный переход состояния обрабатывается (особенно, когда новые состояния добавляются позже), и я знаю, что переходы идентичности всегда будут запрещены.
Я могу устранить ошибку компилятора, раскомментировав строку ниже этой (_ => {}
), но тогда я потеряю преимущество проверки компилятором допустимых переходов, потому что это будет соответствовать любым состояниям, добавленным в будущем.
Я также мог бы решить эту проблему, набрав вручную каждый переход идентификации, например:
(State::Initial, State::Initial) => {}
Это утомительно, и в этот момент я просто сражаюсь с компилятором. Это, вероятно, можно превратить в макрос, поэтому я мог бы сделать что-то вроде:
identity_is_no_op!(State);
Или худший случай:
identity_is_no_op!(State::Initial, State::One, State::Two);
Макрос может автоматически писать этот шаблон в любое время, когда добавляется новое состояние, но это выглядит как ненужная работа, когда шаблон, который я написал, должен охватывать именно тот случай, который я ищу.
- Почему это не работает как написано?
- Какой самый чистый способ сделать то, что я пытаюсь сделать?
Я решил, что макрос второй формы (т.е. identity_is_no_op!(State::Initial, State::One, State::Two);
) на самом деле является предпочтительным решением.
Легко представить себе будущее, в котором я хочу, чтобы некоторые государства что-то делали в случае «без перехода». Использование этого макроса все равно будет иметь желательный эффект от принудительного пересмотра конечного автомата при добавлении новых State
s и просто потребует добавления нового состояния в арглист макроса, если ничего не нужно будет делать. Разумный компромисс ИМО.
Я думаю, что вопрос все еще полезен, потому что поведение меня удивляло как сравнительно нового Рустаха.