类型状态构建器
将有效性编码到类型系统中。
类型状态构建器 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。
普通构建器的问题
普通构建器允许您随时调用 build(),甚至可以在必需字段设置之前调用。缺少的数据随后会以运行时崩溃或 Err 的形式暴露出来。
类型状态构建器会将这项检查提前到编译时:忘记必需步骤只会导致编译失败。
在类型中编码状态
关键在于,让构建器使用标记类型作为泛型参数,以表示哪些步骤已经完成。每当您设置一个字段时,构建器的类型就会发生变化。
只有当每个必需标记都进入其“已设置”状态后,build() 方法才会可用。
struct Missing;
struct Set;使用标记类型作为泛型参数的构建器
为每个必需字段的状态向构建器提供类型参数。PhantomData 会携带标记,而不会存储任何实际数据。
use std::marker::PhantomData;
struct ReqBuilder<H, U> {
url: Option<String>,
method: Option<String>,
_state: PhantomData<(H, U)>,
}初始状态
构造函数返回的构建器中,每个必需标记都是 Missing。此时不存在 build(),因此类型系统知道该对象尚未完成。
impl ReqBuilder<Missing, Missing> {
fn new() -> Self {
ReqBuilder { url: None, method: None, _state: PhantomData }
}
}转换标记
设置器会消耗旧构建器,并返回一个新构建器,其中该字段的标记变为 Set。通过保留另一个标记的泛型类型参数,可以保留它的状态。
impl<U> ReqBuilder<Missing, U> {
fn url(self, url: &str) -> ReqBuilder<Set, U> {
ReqBuilder { url: Some(url.to_string()),
method: self.method, _state: PhantomData }
}
}第二次转换
设置该方法的工作方式相同:将第二个标记从 Missing 变为 Set,同时保持第一个标记不变。
impl<H> ReqBuilder<H, Missing> {
fn method(self, m: &str) -> ReqBuilder<H, Set> {
ReqBuilder { url: self.url,
method: Some(m.to_string()), _state: PhantomData }
}
}仅在全部设置后提供 build()
关键在于,只有 ReqBuilder<Set, Set> 实现了 build()。其他任何状态都没有这个方法,因此调用会导致编译失败。
在方法内部,unwrap 调用绝不会崩溃,因为类型已经证明两个字段都存在。
struct Request { url: String, method: String }
impl ReqBuilder<Set, Set> {
fn build(self) -> Request {
Request { url: self.url.unwrap(), method: self.method.unwrap() }
}
}组合起来
正确的调用链可以顺利编译,因为每次调用都会将构建器推进到 ReqBuilder<Set, Set>,而 build() 正是在该状态下存在。
fn demo() -> Request {
ReqBuilder::new()
.url("https://example.com")
.method("GET")
.build()
}您希望看到的编译错误
跳过必需步骤后,编译器会拒绝编译。在 ReqBuilder<Set, Missing> 上调用 build() 会报告“没有名为 build 的方法”,在程序运行前就捕获遗漏。
// ReqBuilder::new().url("x").build();
// error: no method named `build` found for
// ReqBuilder<Set, Missing>可独立运行的版本
这个最小程序使用一个必需字段,完整展示从开始到结束的状态转换。它可以编译,并打印构建出的值。
use std::marker::PhantomData;
struct Missing; struct Set;
struct B<N> { name: Option<String>, _s: PhantomData<N> }
impl B<Missing> {
fn new() -> Self { B { name: None, _s: PhantomData } }
fn name(self, n: &str) -> B<Set> {
B { name: Some(n.to_string()), _s: PhantomData }
}
}
impl B<Set> {
fn build(self) -> String { self.name.unwrap() }
}
fn main() {
let v = B::new().name("prod").build();
println!("built: {}", v);
}代价与权衡
类型状态构建器通过标记被擦除来提供编译时保证,同时不产生运行时开销。代价是需要更多类型机制,并且随着必需字段增加,实现块的组合数量也会增加。
只有当不完整的构建必须从设计上就不可能发生时,才应使用这种模式。
快速检查
找出类型状态构建器究竟通过什么机制保证构建完整。
回顾
类型状态构建器将每个必需步骤编码为标记类型参数,并由 PhantomData 携带。设置器会消耗构建器,并返回一个标记变为 Set 的新类型。
由于只有所有状态都是 Set 时才实现 build(),忘记步骤会产生编译错误,而不是运行时崩溃,并且完全没有运行时成本。
常见问题解答
「类型状态构建器」课时是免费的吗?
是的 — 「类型状态构建器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。
「类型状态构建器」这节课中我会学到什么?
将有效性编码到类型系统中。 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Learn Rust Coding 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「类型状态构建器」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Learn Rust Coding 课中编写并运行代码吗?
能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 构建器模式
- 新类型模式
- 类型状态构建器
- Deref 与包装器易用性