Rust 用 enum 与模式匹配实现类型安全的状态机:把非法状态变成编译错误

test12026-10-041 次阅读

为什么用 enum 建模状态

订单、设备、连接这些对象都有明确的生命周期。用字符串或整数记状态,编译期管不住非法跳转,bug 往往到运行时才暴露。Rust 的 enum 把「有哪些状态」变成类型,转移函数只能返回合法目标,非法状态在编译期就被拦下。

定义状态与事件

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OrderState {
    Created,
    Paid,
    Shipped,
    Completed,
    Cancelled,
}

#[derive(Debug, Clone, Copy)]
enum OrderEvent {
    Pay,
    Ship,
    ConfirmReceived,
    Cancel,
}

把转移写成函数

转移函数接收当前状态与事件,返回 Result,只有合法组合才成功:

fn transition(state: OrderState, event: OrderEvent) -> Result {
    use OrderEvent::*;
    use OrderState::*;

    let next = match (state, event) {
        (Created, Pay) => Paid,
        (Paid, Ship) => Shipped,
        (Shipped, ConfirmReceived) => Completed,
        (Created, Cancel) | (Paid, Cancel) => Cancelled,
        _ => return Err(format!("非法转移: {:?} + {:?}", state, event)),
    };
    Ok(next)
}

在业务里消费

match 必须穷尽,漏掉某个状态编译器直接报错,等于一份永远不过期的状态文档:

fn describe(s: OrderState) -> &'static str {
    match s {
        OrderState::Created => "待支付",
        OrderState::Paid => "已支付",
        OrderState::Shipped => "已发货",
        OrderState::Completed => "已完成",
        OrderState::Cancelled => "已取消",
    }
}

进阶:用关联数据承载上下文

状态可以携带数据,比如已发货要记下物流单号,类型系统在编译期保证你取得到:

enum Shipment {
    Pending,
    InTransit { tracking_no: String },
    Delivered { signed_by: String },
}

fn tracking_no(s: &Shipment) -> Option<&str> {
    match s {
        Shipment::InTransit { tracking_no } => Some(tracking_no),
        Shipment::Delivered { .. } => Some("已签收"),
        Shipment::Pending => None,
    }
}

工程收益

  • 非法状态成为编译错误,而不是线上事故。
  • 状态流转集中在一处 match,改动时编译器帮你找出所有遗漏分支。
  • 状态与数据绑定,取值前不必做类型断言或空值判断。
T

test1

文章作者

为什么用 enum 建模状态 订单、设备、连接这些对象都有明确的生命周期。用字符串或整数记状态,编译期管不住非法跳转,...

分类
技术
发布时间
2026-10-04
字数
约 1596 字
阅读
1 次