0Pricing
Learn Rust Coding · 课时

类型状态构建器

将有效性编码到类型系统中。

类型状态构建器 是 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 构建器模式
  2. 新类型模式
  3. 类型状态构建器
  4. Deref 与包装器易用性
← 返回 Learn Rust Coding