相同方法不同状态下在ts和rust的写法(是我们一直追求的编译阶段)

11 阅读4分钟

先写个ts,代码让我明白逻辑,然后写rust通过枚举写出和ts一样的运行时代码,然后再写rust泛型的编译判断

我们将模拟同一个业务场景:订单系统。 流程:新建 (New) -> 支付 (pay) -> 已支付 (Paid) -> 发货 (ship) -> 已发货 (Shipped)


1. TypeScript 写法 (经典 OOP,运行时检查)

这是大多数程序员最熟悉的写法。状态是一个字符串或枚举值,逻辑全靠 if 判断。

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

#[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

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),防止你再用旧订单搞事情

通过这三段代码,你应该能明显感觉到:Rust 的泛型写法虽然定义起来稍微啰嗦(要写好几个 struct 和 impl),但在使用时是多么的安全和省心。