Back to Blog

Rust 状态机构建实战:Typestate 模式类型设计指南

2026/8/303 min read

Rust 状态机构建实战:Typestate 模式类型设计指南

写 Rust 的时候,状态管理是个绕不过去的话题。用 enum 做状态机?容易陷入 match 地狱。用 AtomicU8 加锁?并发安全了但逻辑脆弱。这里分享一种更健壮的方案:Typestate 模式,把状态直接 encode 进类型系统,让非法状态在编译期就报错。

什么是 Typestate 模式

Typestate 模式的核心思想是:用一个新类型来表示当前状态,每种状态都是独立的类型,而不是枚举里的一个分支。

// 传统 enum 状态机
enum Connection {
    Disconnected,
    Connecting,
    Connected,
}

// Typestate 模式
struct Disconnected;
struct Connecting {
    timeout: Duration,
}
struct Connected {
    socket: TcpStream,
}

看起来麻烦?好处是状态转换变成了方法调用,每个方法只能在自己的 receiver 类型上调用。

为什么选 Typestate

enum 状态机的最大问题是:你可以从任何状态跳到任何其他状态,编译器不管。Typestate 通过类型签名强制规定了合法转换路径。

比如 Connected::close(self) -> Disconnected,只有 Connected 能调用,返回的是 Disconnected,类型系统确保你不会在连接未建立时就尝试关闭。

这种模式的代价是类型数量随状态增长,但换来的是状态机逻辑的完全类型安全。

状态转换的类型设计

状态转换的方法设计有两个关键原则:

  1. 输入类型明确:状态转换方法的 receiver 必须是当前状态,返回值是新状态类型。
  2. 数据传递安全:状态携带的数据要在转换时合理转移,不能丢失也不能重复持有。
impl Connecting {
    fn with_timeout(timeout: Duration) -> Self {
        Connecting { timeout }
    }

    // 转换成功
    fn connect(self, socket: TcpStream) -> Connected {
        Connected { socket }
    }

    // 转换失败
    fn timeout(self) -> Disconnected {
        Disconnected
    }
}

这里 self 被 consume,转换后原状态不可用,防止重复操作。

组合多个状态机

实际业务往往有多个状态机协作。比如一个请求处理流程:连接建立、身份验证、数据同步。

可以用嵌套结构体表示组合状态:

struct ConnectionState<T> {
    conn: T,
}

type AuthenticatedConnection = ConnectionState<Connected>;
type ValidatedConnection = ConnectionState<AuthenticatedConnection>;

这样 ValidatedConnection 同时包含了连接和认证的状态信息,转换时需要同时推进两层状态。

常见陷阱与规避

  1. 过度拆分:状态太多会导致类型爆炸。保持每个状态有足够的语义区分度,不要把细微差别也做成独立类型。
  2. 忽略中间状态:连接超时重试、断线重连等场景需要引入过渡状态,不要试图用单个状态覆盖所有情况。
  3. 数据所有权混乱:状态转换时数据的所有权必须清晰,谁持有资源要一目了然。

适用场景

Typestate 模式特别适合以下场景:

  • 网络协议实现,状态转换严格
  • 数据库连接池管理
  • 事务处理流程
  • 游戏对象生命周期管理

如果你的状态机状态少且转换简单,enum 够用。但如果状态复杂、转换规则严格,Typestate 模式能显著降低运行时错误。

总结

Typestate 模式用编译期类型检查替代了运行时的状态校验,代价是更多类型定义,收益是更可靠的状态机实现。在 Rust 中实践这个模式,关键是合理设计状态类型和转换方法,让类型系统成为你的守护,而不是负担。