请考虑以下代码示例(playground)。
#[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)
边。重要的是,我特别想要这种情况,因为我想利用编译器来确保处理每个可能的状态转换(尤其是稍后添加新状态时),并且我知道身份转换将始终是无操作的。
我可以通过取消注释该行下面的行(_ => {}
的注释来解决编译器错误,但是随后我失去了让编译器检查有效转换的优势,因为这将在将来添加的任何状态上都匹配。 / p>
我还可以通过手动键入每个身份转换来解决此问题,例如:
(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
时强制重新访问状态机的预期效果,并且如果不需要执行任何操作,仅需要将新状态添加到宏arglist中即可。 IMO的合理妥协。
我认为这个问题仍然有用,因为作为一个相对较新的Rustacean,这种行为令我感到惊讶。
答案 0 :(得分:3)
为什么这不能按原样进行?
因为在确定match
是否详尽时,Rust编译器无法考虑保护表达式。一旦有了警卫,便会认为警卫可能会失败。
请注意,这与refutable and irrefutable patterns之间的区别无关。 if
警卫不是该模式的一部分,而是match
语法的一部分。
做我想做的最干净的方法是什么?
列出所有可能的组合。宏可以稍微缩短编写模式的时间,但是您不能使用宏来替换整个match
臂。