新类型模式
为类型增加包装,提升安全性和清晰度。
新类型模式 是 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 反馈 — 无需本地设置。