跳到主要内容

Newtype 模式

问题

什么是 Newtype 模式?它在 Rust 中有什么用途?

答案

Newtype 是用单字段元组结构体包装已有类型,创建一个语义不同的新类型:

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

// 不能混用,即使底层都是 f64
fn speed(distance: Meters, time: Seconds) -> f64 {
distance.0 / time.0
}
// speed(Seconds(1.0), Meters(100.0)) // ❌ 参数顺序错误,编译报错

核心用途

1. 类型安全,防止混淆

struct UserId(u64);
struct OrderId(u64);

fn get_order(user_id: UserId, order_id: OrderId) -> Order {
// 参数不会搞混
}

2. 为外部类型实现外部 Trait(绕过孤儿规则)

// 不能直接为 Vec<T> 实现 Display(违反孤儿规则)
// 但可以用 Newtype 包装
struct Wrapper(Vec<String>);

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

3. 封装实现细节

pub struct Email(String);

impl Email {
pub fn new(s: &str) -> Result<Self, String> {
if s.contains('@') {
Ok(Email(s.to_string()))
} else {
Err("无效邮箱".into())
}
}

pub fn as_str(&self) -> &str { &self.0 }
}
// 外部无法直接 Email("invalid".into()),必须通过 new() 验证

4. 零成本抽象

Newtype 在编译后与内部类型完全相同,没有运行时开销(#[repr(transparent)] 可显式保证)。

配合 Deref 使用

use std::ops::Deref;

struct Email(String);

impl Deref for Email {
type Target = str;
fn deref(&self) -> &str { &self.0 }
}

let email = Email("user@example.com".into());
println!("{}", email.len()); // 自动 Deref 到 &str
println!("{}", email.contains('@')); // str 的方法都能用
Deref 反模式

不要随意为 Newtype 实现 Deref。如果你想限制 API(如 Email 不应暴露 replace() 等修改方法),就不应实现 Deref,而是手动选择暴露哪些方法。


常见面试问题

Q1: Newtype 和类型别名有什么区别?

答案

// 类型别名:完全等价,编译器不区分
type Km = f64;
type Miles = f64;
let d: Km = 100.0;
let m: Miles = d; // ✅ 可以互换

// Newtype:新类型,编译器严格区分
struct Km(f64);
struct Miles(f64);
// let m: Miles = Km(100.0); // ❌ 类型不同

类型别名只是起别名,Newtype 创建了真正的新类型。

Q2: Newtype 有运行时开销吗?

答案

没有。Newtype struct Wrapper(T)T 有完全相同的内存布局和性能。可以加 #[repr(transparent)] 显式保证 ABI 兼容,这在 FFI 场景中很重要。

Q3: 什么是孤儿规则?Newtype 如何绕过它?

答案

孤儿规则:只能为"你定义的类型"实现"你定义的 Trait",或者为"任意类型"实现"你定义的 Trait"。不能为"外部类型"实现"外部 Trait"。

Newtype 通过包装外部类型创建"你定义的类型",从而绕过限制:

struct MyVec(Vec<String>);  // 新类型
impl Display for MyVec { /* 合法 */ }

相关链接