为什么带有保护子句的匹配模式不详尽?

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)

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

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

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

很容易想象未来我确实希望一些州在“不过渡”的情况下做一些事情。使用这个宏仍然会达到预期的效果,即在State添加新的情况下强制重新访问状态机,并且如果不需要执行任何操作,则只需将新状态添加到宏参数列表中即可。IMO 的合理妥协。

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

DK.*_*DK. 4

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

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

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

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

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

  • 或者如果您确信自己做得正确,则添加 `_ => unreachable!()`。 (7认同)