在 Rust 中,newtype 模式(有时也被称为 "type alias with zero-sized type" 或 "wrapper type")是一种使用结构体(通常只包含一个字段)来定义新类型的模式。使用 newtype 模式,可以为现有类型定义新的类型,并为这些新类型提供自定义行为或限制。这样做有助于在编译时捕获类型错误,并提高代码的可读性和可维护性。
直接看个简单的例子。假设有两个函数,分别根据用户名获取用户 id 或根据部门名获取部门 id:
fn get_id_by_username(username: &str) -> u32 {
// ...
123
}
fn get_id_by_orgname(orgname: &str) -> u32 {
// ...
456
}
这两个函数返回的 id 都是 u32 类型,因此,如果你还有一个删除用户的函数,编译器无法帮你识别这种错误调用:
fn delete_user(user_id: u32) {
// ...
}
// 堆代码 duidaima.com
let org_id = get_id_by_orgname("人资部");
delete_user(org_id); // 错误地使用了部门 id
这时就可以使用 newtype 模式解决。直白地说,就是使用元组结构体的方式将已有的类型包裹起来:
struct UserId(pub u32);
struct OrgId(pub u32);
fn get_id_by_username(username: &str) -> UserId {
// ...
UserId(123)
}
fn get_id_by_orgname(orgname: &str) -> OrgId {
// ...
OrgId(456)
}
fn delete_user(user_id: UserId) {
// ...
}
此时,如果你进行了误调用:
let org_id = get_id_by_orgname("人资部");
delete_user(org_id);
编译器可以帮你检查到错误:

你可能会疑惑 newtype 模式和类型别名有什么区别?其实 最大的区别是 newtype 模式会产生新类型,而类型别名不会。 因此后者也不能在编译时捕获类型错误。别名最大的用处是提高代码的可读性,减少类型名称的重复。
type Kilometers = i32;
type LongType = Box<dyn Fn() + Send + 'static>;
newtype 模式既然产生了新类型,自然可以为类型增加方法,享受类型带来的所有好处。但是有一点好处千万不要忽视,就是:既然产生了新类型,就可以在遵循孤儿规则的前提下,为外部类型实现外部 trait。