name: m05-type-driven description: “关键:用于类型驱动设计。触发词:类型状态、PhantomData、新类型模式、标记特征、建造者模式、使无效状态不可表示、编译时验证、密封特征、ZST、类型状态、新类型模式、类型驱动设计” user-invocable: false
类型驱动设计
层级 1:语言机制
核心问题
类型系统如何防止无效状态?
在诉诸运行时检查之前,请思考:
- 编译器能捕获这个错误吗?
- 能否使无效状态不可表示?
- 类型能否编码这个不变量?
错误 → 设计问题
| 模式 | 不要只说 | 而是问 |
|---|---|---|
| 原始类型迷恋 | “它只是一个字符串” | 这个值代表什么? |
| 布尔标志 | “加一个 is_valid 标志” | 状态能否成为类型? |
| 到处使用 Optional | “检查是否为 None” | 缺失状态真的可能吗? |
| 运行时验证 | “无效则返回 Err” | 我们能在构造时验证吗? |
思考提示
在添加运行时验证之前:
-
类型能否编码约束?
- 数值范围 → 有界类型或新类型
- 有效状态 → 类型状态模式
- 语义含义 → 新类型
-
何时可以进行验证?
- 构造时 → 已验证的新类型
- 状态转换时 → 类型状态
- 仅在运行时 → 带有清晰错误的 Result
-
谁需要知道这个不变量?
- 编译器 → 类型级编码
- API 使用者 → 清晰的类型签名
- 仅运行时 → 文档
向上追溯 ↑
当类型设计不清晰时:
"需要验证电子邮件格式"
↑ 问:这是一个领域值对象吗?
↑ 检查:m09-domain (Email 作为值对象)
↑ 检查:domain-* (验证要求)
| 情况 | 追溯至 | 问题 |
|---|---|---|
| 创建什么类型 | m09-domain | 领域模型是什么? |
| 状态机设计 | m09-domain | 什么是有效转换? |
| 标记特征用法 | m04-zero-cost | 静态还是动态分发? |
向下追溯 ↓
从设计到实现:
"需要原始类型的类型安全包装"
↓ 新类型:struct UserId(u64);
"需要编译时状态验证"
↓ 类型状态:Connection<Connected>
"需要追踪幻影类型参数"
↓ PhantomData:PhantomData<T>
"需要能力标记"
↓ 标记特征:trait Validated {}
"需要渐进式构造"
↓ 建造者:Builder::new().field(x).build()
快速参考
| 模式 | 目的 | 示例 |
|---|---|---|
| 新类型 | 类型安全 | struct UserId(u64); |
| 类型状态 | 状态机 | Connection<Connected> |
| PhantomData | 变体/生命周期 | PhantomData<&'a T> |
| 标记特征 | 能力标志 | trait Validated {} |
| 建造者 | 渐进式构造 | Builder::new().name("x").build() |
| 密封特征 | 防止外部实现 | mod private { pub trait Sealed {} } |
模式示例
新类型
struct Email(String); // 不只是任意字符串
impl Email {
pub fn new(s: &str) -> Result<Self, ValidationError> {
// 验证一次,永远信任
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> { ... }
}
决策指南
| 需求 | 模式 |
|---|---|
| 原始类型的类型安全 | 新类型 |
| 编译时状态验证 | 类型状态 |
| 生命周期/变体标记 | PhantomData |
| 能力标志 | 标记特征 |
| 渐进式构造 | 建造者 |
| 封闭的实现集合 | 密封特征 |
| 零大小类型标记 | ZST 结构体 |
反模式
| 反模式 | 为何不好 | 更好的方案 |
|---|---|---|
| 用布尔标志表示状态 | 运行时错误 | 类型状态 |
| 用字符串表示语义类型 | 无类型安全 | 新类型 |
| 用 Option 表示未初始化 | 不变量不清晰 | 建造者 |
| 带有不变量的公共字段 | 不变量可能被破坏 | 私有字段 + 已验证的 new() |
相关技能
| 何时 | 参见 |
|---|---|
| 领域建模 | m09-domain |
| 特征设计 | m04-zero-cost |
| 构造函数中的错误处理 | m06-error-handling |
| 反模式 | m15-anti-pattern |