关于 m05-type-driven
面向 Rust 设计的技能,讲解如何用类型表达不变量并避免无效状态。内容涵盖从 newtype 到 Builder 和类型状态模式的选择。
主要功能
- 01用类型表达约束
- 02选择常见类型模式
- 03应用类型状态设计
- 04识别类型设计反模式
适用场景
- 01用 newtype 表示语义值
- 02建模编译期状态转换
- 03选择已验证构造模式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ actionbook/ rust-skills --skill m05-type-driven安装后在 Claude Code 中输入/m05-type-driven确认已加载。
详细介绍
类型驱动设计
第 1 层:语言机制
核心问题
类型系统如何防止无效状态?
在使用运行时检查之前:
- 编译器能否捕获此错误?
- 能否让无效状态无法表示?
- 类型能否编码这一不变量?
错误 → 设计问题
| 模式 | 不要只说 | 转而询问 |
|---|---|---|
| 基本类型痴迷 | “这只是一个字符串” | 这个值代表什么? |
| 布尔标志 | “添加一个 is_valid 标志” | 能否将状态表示为类型? |
| 到处使用可选值 | “检查是否为 None” | 真的可能缺失吗? |
| 运行时验证 | “无效时返回 Err” | 能否在构造时验证? |
思考提示
在添加运行时验证之前:
-
类型能否编码约束?
- 数值范围 → 有界类型或 newtype
- 有效状态 → 类型状态模式
- 语义含义 → newtype
-
何时可以进行验证?
- 构造时 → 已验证的 newtype
- 状态转换时 → 类型状态
- 只能在运行时 → 返回明确错误的 Result
-
谁需要了解这个不变量?
- 编译器 → 类型级编码
- API 用户 → 清晰的类型签名
- 仅运行时 → 文档
向上追溯 ↑
类型设计不明确时:
"Need to validate email format"
↑ Ask: Is this a domain value object?
↑ Check: m09-domain (Email as Value Object)
↑ Check: domain-* (validation requirements)
| 情况 | 追溯到 | 问题 |
|---|---|---|
| 要创建哪些类型 | m09-domain | 领域模型是什么? |
| 状态机设计 | m09-domain | 有哪些有效转换? |
| 标记 trait 的使用 | m04-zero-cost | 静态还是动态分派? |
向下追溯 ↓
从设计到实现:
"Need type-safe wrapper for primitives"
↓ Newtype: struct UserId(u64);
"Need compile-time state validation"
↓ Type State: Connection<Connected>
"Need to track phantom type parameters"
↓ PhantomData: PhantomData<T>
"Need capability markers"
↓ Marker Trait: trait Validated {}
"Need gradual construction"
↓ Builder: Builder::new().field(x).build()
快速参考
| 模式 | 用途 | 示例 |
|---|---|---|
| Newtype | 类型安全 | struct UserId(u64); |
| 类型状态 | 状态机 | Connection<Connected> |
| PhantomData | 方差/生命周期 | PhantomData<&'a T> |
| 标记 Trait | 能力标志 | trait Validated {} |
| Builder | 渐进式构造 | Builder::new().name("x").build() |
| 密封 Trait | 阻止外部实现 | mod private { pub trait Sealed {} } |
模式示例
Newtype
struct Email(String); // Not just any string
impl Email {
pub fn new(s: &str) -> Result<Self, ValidationError> {
// Validate once, trust forever
validate_email(s)?;
Ok(Self(s.to_string()))
}
}
类型状态
struct Connection<State>(TcpStream, PhantomData<State>);
struct Disconnected;
struct Connected;
struct Authenticated;
impl Connection<Disconnected> {
fn connect(self) -> Connection<Connected> { ... }
}
impl Connection<Connected> {
fn authenticate(self) -> Connection<Authenticated> { ... }
}
决策指南
| 需求 | 模式 |
|---|---|
| 基本类型的类型安全 | Newtype |
| 编译时状态验证 | 类型状态 |
| 生命周期/方差标记 | PhantomData |
| 能力标志 | 标记 Trait |
| 渐进式构造 | Builder |
| 封闭的实现集合 | 密封 Trait |
| 零大小类型标记 | ZST struct |
反模式
| 反模式 | 问题 | 更好的做法 |
|---|---|---|
| 用布尔标志表示状态 | 运行时错误 | 类型状态 |
| 用字符串表示语义类型 | 没有类型安全 | Newtype |
| 用 Option 表示未初始化 | 不变量不明确 | Builder |
| 带不变量的公开字段 | 违反不变量 | 私有字段 + 经验证的 new() |
相关技能
| 适用场景 | 查看 |
|---|---|
| 领域建模 | m09-domain |
| Trait 设计 | m04-zero-cost |
| 构造函数中的错误处理 | m06-error-handling |
| 反模式 | m15-anti-pattern |
评论
支持 Markdown