Mic*_*ard 5 pattern-matching rust
考虑以下代码示例(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);
}
Run Code Online (Sandbox Code Playgroud)
在该StateMachine::transition方法中,所提供的代码无法编译:
#[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);
}
Run Code Online (Sandbox Code Playgroud)
但这正是我想要匹配的模式!连同(One, One)边和(Two, Two)边。重要的是,我特别想要这种情况,因为我想利用编译器来确保处理每个可能的状态转换(特别是稍后添加新状态时),并且我知道身份转换将始终是无操作的。
我可以通过取消注释该行 ( ) 下面的行来解决编译器错误,_ => {}但随后我就失去了让编译器检查有效转换的优势,因为这将匹配将来添加的任何状态。
我还可以通过手动输入每个身份转换来解决此问题,例如:
(State::Initial, State::Initial) => {}
Run Code Online (Sandbox Code Playgroud)
这很乏味,那时我只是在与编译器作斗争。这可能会变成一个宏,所以我可能会这样做:
identity_is_no_op!(State);
Run Code Online (Sandbox Code Playgroud)
或者最坏的情况:
identity_is_no_op!(State::Initial, State::One, State::Two);
Run Code Online (Sandbox Code Playgroud)
每当添加新状态时,宏都可以自动编写此样板,但是当我编写的模式应该涵盖我正在寻找的确切情况时,这感觉像是不必要的工作。
我决定第二种形式的宏(即identity_is_no_op!(State::Initial, State::One, State::Two);)实际上是首选解决方案。
很容易想象未来我确实希望一些州在“不过渡”的情况下做一些事情。使用这个宏仍然会达到预期的效果,即在State添加新的情况下强制重新访问状态机,并且如果不需要执行任何操作,则只需将新状态添加到宏参数列表中即可。IMO 的合理妥协。
我认为这个问题仍然有用,因为作为一个相对较新的 Rustacean,这种行为让我感到惊讶。
为什么这不像写的那样工作?
match因为 Rust 编译器在确定 a 是否详尽时无法考虑保护表达式。一旦你有了守卫,它就会假设守卫可能会失败。
请注意,这与可反驳模式和不可反驳模式之间的区别无关。 if守卫不是模式的一部分,它们是match语法的一部分。
做我想做的事情最干净的方法是什么?
列出所有可能的组合。宏可以稍微缩短编写模式的时间,但是您不能使用宏来替换整个match手臂。
| 归档时间: |
|
| 查看次数: |
1597 次 |
| 最近记录: |