先写个ts,代码让我明白逻辑,然后写rust通过枚举写出和ts一样的运行时代码,然后再写rust泛型的编译判断
我们将模拟同一个业务场景:订单系统 。 流程:新建 (New) -> 支付 (pay) -> 已支付 (Paid) -> 发货 (ship) -> 已发货 (Shipped)。
1. TypeScript 写法 (经典 OOP,运行时检查)
这是大多数程序员最熟悉的写法。状态是一个字符串或枚举值,逻辑全靠 if 判断。
TypeScript
typescript
// 定义状态类型
type OrderStatus = 'New' | 'Paid' | 'Shipped';
class Order {
id: number;
status: OrderStatus; // 状态只是一个数据字段
constructor(id: number) {
this.id = id;
this.status = 'New'; // 默认为 New
}
// 支付方法
pay() {
// 【痛点】:必须在运行时写代码检查当前状态
if (this.status !== 'New') {
throw new Error(`错误:订单 ${this.id} 状态是 ${this.status},不能支付!`);
}
console.log(`订单 ${this.id} 支付成功`);
this.status = 'Paid'; // 修改内部状态
}
// 发货方法
ship() {
// 【痛点】:如果你忘了写这个 check,或者写错了,编译时不会报错,运行时会炸
if (this.status !== 'Paid') {
throw new Error(`错误:订单 ${this.id} 没支付,不能发货!`);
}
console.log(`订单 ${this.id} 发货成功`);
this.status = 'Shipped';
}
}
// --- 测试 ---
const order = new Order(1);
// 1. 正常流程
order.pay();
order.ship();
// 2. 错误流程 (编译器拦不住,运行才会报错)
// order.ship(); // 抛出 Error: 错误:订单 1 状态是 Shipped,不能发货!
2. Rust 枚举写法 (模拟 TS 逻辑,运行时检查)
这是 Rust 中处理动态状态的标准写法。逻辑和 TS 几乎一样,只是用 match 替代了 if,用 Result 替代了 throw Error。
特点: 代码写在同一个 impl 里,可以编译通过非法调用的代码(比如连续调用两次 pay),必须运行起来才知道错没错。
Rust
rust
#[derive(Debug, PartialEq)]
enum Status {
New,
Paid,
Shipped,
}
#[derive(Debug)]
struct Order {
id: u64,
status: Status, // 状态是结构体的一个字段
}
impl Order {
fn new(id: u64) -> Self {
Self {
id,
status: Status::New,
}
}
// 必须返回 Result,因为运行时可能会失败
fn pay(&mut self) -> Result<(), String> {
// 【运行时检查】:和 TS 一样,必须手动判断状态
match self.status {
Status::New => {
println!("订单 {} 支付成功", self.id);
self.status = Status::Paid; // 修改自身字段
Ok(())
}
_ => Err("只有新订单才能支付".to_string()),
}
}
fn ship(&mut self) -> Result<(), String> {
// 【运行时检查】
match self.status {
Status::Paid => {
println!("订单 {} 发货成功", self.id);
self.status = Status::Shipped;
Ok(())
}
_ => Err("只有已支付的订单才能发货".to_string()),
}
}
}
fn main() {
let mut order = Order::new(1001);
// 1. 正常流程 (需要处理 Result)
if let Ok(_) = order.pay() {
let _ = order.ship();
}
// 2. 错误流程
// 编译器完全允许你写这行代码,只有跑起来通过 Err 告诉你错了
// let _ = order.ship(); // 运行时返回 Err
}
3. Rust 泛型 + PhantomData (Type State,编译时检查)
这是 Rust 的"完全体"。状态不再是字段里的值 ,而是依附在结构体上的类型。
特点: 方法被拆分到不同的 impl 块。非法调用的代码连编译都过不去。
Rust
rust
use std::marker::PhantomData;
// --- 1. 定义状态 (空结构体,只做类型标签) ---
struct New;
struct Paid;
struct Shipped;
// --- 2. 定义核心结构体 (带有泛型 State) ---
// 注意:这里没有 `status` 字段了!状态由 State 类型本身决定
struct Order<State> {
id: u64,
// 这是一个 0 大小的字段,只为了告诉编译器 State 是啥
_marker: PhantomData<State>,
}
// --- 3. 针对不同状态实现不同的方法 ---
// 只有 <New> 状态才有 pay 方法
impl Order<New> {
fn new(id: u64) -> Self {
Self { id, _marker: PhantomData }
}
// 动作:New -> Paid
// 注意:输入是 self (消耗掉旧订单),输出是 Order<Paid> (返回新订单)
fn pay(self) -> Order<Paid> {
println!("订单 {} 支付成功", self.id);
Order {
id: self.id,
_marker: PhantomData
}
}
}
// 只有 <Paid> 状态才有 ship 方法
impl Order<Paid> {
// 动作:Paid -> Shipped
fn ship(self) -> Order<Shipped> {
println!("订单 {} 发货成功", self.id);
Order {
id: self.id,
_marker: PhantomData
}
}
}
// 只有 <Shipped> 状态才有 finish 方法
impl Order<Shipped> {
fn finish(self) {
println!("订单 {} 流程结束", self.id);
}
}
fn main() {
// --- 1. 正常流程 ---
let order_new = Order::<New>::new(1001); // 类型: Order<New>
let order_paid = order_new.pay(); // 类型: Order<Paid>
let order_shipped = order_paid.ship(); // 类型: Order<Shipped>
order_shipped.finish();
// --- 2. 错误流程 (高能预警) ---
// 如果你试图把上面的流程打乱,比如直接对 New 状态发货:
/* let order = Order::<New>::new(1002);
order.ship();
*/
// 💥 编译器直接报错:
// error[E0599]: no method named `ship` found for struct `Order<New>`
// 意思:兄弟,Order<New> 这个类型根本就没有 ship 这个函数,你想啥呢?
}
终极对比总结
| 维度 | TS / Rust Enum 写法 | Rust 泛型 (Type State) 写法 |
|---|---|---|
| 状态本质 | 数据 (Data) 存储在内存的一个变量里 ("New", 0) |
类型 (Type) 写在代码签名里 (<New>, <Paid>) |
| 逻辑位置 | 集中在一起 所有方法都在一个 class/impl 里,内部用 if/match 分流 | 分散隔离 pay 只存在于 New 的实现块里 ship 只存在于 Paid 的实现块里 |
| 错误发现 | 运行时 (Runtime) 跑起来抛异常或返回 Err | 编译时 (Compile Time) 代码还没跑,编译器就标红了 |
| IDE 体验 | 全显示 敲 order. 会看到 pay 和 ship,容易误点 |
智能筛选 如果是新订单,敲 order. 根本看不到 ship |
| 所有权 | 状态可变 对象一直是那个对象,只是改了个字段值 | 旧状态销毁 调用 pay() 后,旧的 order 变量直接失效 (Move),防止你再用旧订单搞事情 |
Export to Sheets
通过这三段代码,你应该能明显感觉到:Rust 的泛型写法虽然定义起来稍微啰嗦(要写好几个 struct 和 impl),但在使用时是多么的安全和省心。