0Pricing
Learn Rust Coding · 课时

新类型模式

为类型增加包装,提升安全性和清晰度。

新类型模式 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。

什么是 Newtype?

Newtype 是一种只有一个字段的元组结构体,它包装现有类型,为其提供独立的身份。struct Meters(f64) 是一个全新的类型,即使它内部保存的是普通的 f64。

这个包装类型不会产生运行时开销,但可以让编译器强制检查原始基本类型无法表达的含义。

struct Meters(f64);
struct Seconds(f64);

防止单位混淆

原始基本类型很容易混淆。如果距离和时间都是 f64,调用函数时交换它们不会受到任何阻止。

分别用各自的 Newtype 包装它们,可以让这类错误在编译时就被发现,而不是变成悄无声息的 bug。

fn speed(d: Meters, t: Seconds) -> f64 {
    d.0 / t.0
}
// speed(Seconds(2.0), Meters(10.0)) -> compile error

访问内部值

您可以通过元组索引 .0 获取被包装的值。许多 Newtype 还会提供方法,或实现 From,以便进行更方便的转换。

struct UserId(u64);
impl UserId {
    fn value(&self) -> u64 { self.0 }
}
fn main() {
    let id = UserId(42);
    println!("id = {}", id.value());
}

封装不变量

将内部字段设为私有,并在构造函数中进行验证。这样,Newtype 的任何值都能保证有效,下游代码无需再次检查。

pub struct Email(String);
impl Email {
    pub fn new(s: &str) -> Option<Email> {
        if s.contains('@') {
            Some(Email(s.to_string()))
        } else {
            None
        }
    }
}

孤儿规则

Rust 禁止为外部类型实现外部 trait。您不能编写 impl Display for Vec<T>,因为 trait 和类型都不归您所有。

这条规则确保了不同 crate 之间的 trait 一致性,但有时也会阻止一些有用的实现。

Newtype 绕过孤儿规则

由于 newtype 定义在您的 crate 中,您现在拥有一个本地类型,可以为它实现任意 trait。这是应对孤儿规则的标准方法。

use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

可运行的 Wrapper Display

这里让上一场景中的包装类型真正发挥作用。我们拥有 Wrapper,因此可以为它实现 Display,程序也就能打印连接后的列表。

use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}
fn main() {
    let w = Wrapper(vec!["a".into(), "b".into()]);
    println!("{}", w);
}

限制 API 接口

包装一个功能强大的类型,可以让您只暴露其中安全的子集。NonEmptyVec 可以隐藏会使其变为空的可变方法,从而保持其不变量。

pub struct NonEmptyVec<T>(Vec<T>);
impl<T> NonEmptyVec<T> {
    pub fn new(first: T) -> Self {
        NonEmptyVec(vec![first])
    }
    pub fn first(&self) -> &T {
        &self.0[0]
    }
}

零成本抽象

只有一个字段的 newtype 与被包装值具有相同的内存布局。编译器会将包装类型优化掉,因此这里的安全性在运行时确实不需要付出代价。

添加 #[repr(transparent)] 可保证布局完全相同,这对 FFI 很重要。

#[repr(transparent)]
struct Celsius(f64);

为 Newtype 派生 Trait

Newtype 通常会派生标准 trait,使其在适当情况下表现得像内部值一样。派生 trait 能让它们更便于用作键、进行比较以及输出调试信息。

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct ProductId(u32);
fn main() {
    let a = ProductId(7);
    let b = a.clone();
    println!("{:?} == {:?}: {}", a, b, a == b);
}

Newtype 与类型别名

不要把 newtype 与类型别名混淆。type Meters = f64 只是一个名称;它仍然是 f64,不会提供额外的安全性。

newtype struct Meters(f64) 则是真正独立的类型,编译器可以将它与其他类型区分开来。

type Km = f64;       // alias: interchangeable with f64
struct Mi(f64);      // newtype: distinct from f64

快速检查

判断为什么 newtype 能解决类型别名无法解决的问题。

回顾

Newtype 模式会将现有类型包装在只有一个字段的元组结构体中,以零运行时成本获得独立的身份。它可以防止值混用,将不变量封装在私有字段之后,并绕过孤儿规则,让您能够实现外部 trait。

与类型别名不同,newtype 是真正独立的类型。

常见问题解答

「新类型模式」课时是免费的吗?

是的 — 「新类型模式」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。

「新类型模式」这节课中我会学到什么?

为类型增加包装,提升安全性和清晰度。 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Learn Rust Coding 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「新类型模式」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Learn Rust Coding 课中编写并运行代码吗?

能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

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