首页 > 解决方案 > 为什么带有保护子句的匹配模式并不详尽?

问题描述

考虑以下代码示例(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);

宏可以在任何时候添加新状态时自动编写此样板,但是当我编写的模式应该涵盖我正在寻找的确切情况时,这感觉像是不必要的工作。

  1. 为什么这不像写的那样工作?
  2. 做我想做的最干净的方法是什么?

我已经决定第二种形式的宏(即identity_is_no_op!(State::Initial, State::One, State::Two);)实际上是首选的解决方案。

很容易想象未来我确实希望某些州在“无过渡”的情况下做某事。使用这个宏仍然会产生预期的效果,即在添加新的 s 时强制重新访问状态机,State并且如果不需要做任何事情,只需将新状态添加到宏 arglist 中。一个合理的妥协IMO。

我认为这个问题仍然有用,因为作为一个相对较新的 Rustacean,这种行为让我感到惊讶。

标签: rustpattern-matching

解决方案


为什么这不像写的那样工作?

match因为在确定 a是否详尽时,Rust 编译器无法考虑防护表达式。只要你有一个守卫,它就假定守卫可能会失败。

请注意,这与可反驳和不可反驳模式之间的区别无关。 if守卫不是模式的一部分,它们是match语法的一部分。

做我想做的最干净的方法是什么?

列出所有可能的组合。宏可以稍微缩短编写模式的时间,但您不能使用宏来替换整个match手臂。


推荐阅读