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);
}
在该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);
)实际上是首选的解决方案。
很容易想象未来我确实希望某些州在“无过渡”的情况下做某事。使用这个宏仍然会产生预期的效果,即在添加新的 s 时强制重新访问状态机,State
并且如果不需要做任何事情,只需将新状态添加到宏 arglist 中。一个合理的妥协IMO。
我认为这个问题仍然有用,因为作为一个相对较新的 Rustacean,这种行为让我感到惊讶。
解决方案
为什么这不像写的那样工作?
match
因为在确定 a是否详尽时,Rust 编译器无法考虑防护表达式。只要你有一个守卫,它就假定守卫可能会失败。
请注意,这与可反驳和不可反驳模式之间的区别无关。 if
守卫不是模式的一部分,它们是match
语法的一部分。
做我想做的最干净的方法是什么?
列出所有可能的组合。宏可以稍微缩短编写模式的时间,但您不能使用宏来替换整个match
手臂。
推荐阅读
- java - 任务:app:mergeExtDexDebug在运行反应本机的android应用程序中失败
- spring - 清理数据库时 Flyway 卡住(Postgres 11)
- c++ - 读取文件 C++ 时出现奇怪的错误
- mysql - 显示查询 mysql 条件
- python - 如何在 Fasttext 监督分类模型中为每个标签获取最重要的标记?
- c# - 我的 IntelliSense 没有显示任何自动填充,即使代码仍然有效
- windows - SetWindowPos 如何影响所有者窗口的 Z 顺序?
- javascript - 如果 array2 不具有相同的对象键值,则从对象数组 (array1) 中删除对象
- python - 用while在几个线程中运行Python函数
- angular - 基于公共布尔数组的 Ionic Auth Gaurd canLoad