
newtype、never 类型:类型系统还能这么玩?
Rust 的类型系统,表面上看着挺正常:整数、字符串、结构体、枚举...
但深入之后你会发现:这玩意儿能玩出花来。
我第一次看到 ! 这个类型时,整个人都懵了:"这是个啥?感叹号?"
后来才知道,这玩意儿叫 never type,代表"永远不会返回"。今天咱们就来扒一扒 Rust 类型系统里这些"高级玩家"。
newtype 模式:用元组结构体包装现有类型,获得类型安全。
为什么需要? 看个例子:
// ❌ 没有类型安全
fn login(username: String, password: String) {}
fn register(username: String, password: String) {}
fn main() {
let user = String::from("larry");
let pass = String::from("123456");
// ❌ 参数顺序错了,但编译器不报错!
login(pass, user); // 密码当用户名传了...
}
用 newtype 解决:
// ✅ 类型安全
struct Username(String);
struct Password(String);
fn login(username: Username, password: Password) {}
fn register(username: Username, password: Password) {}
fn main() {
let user = Username(String::from("larry"));
let pass = Password(String::from("123456"));
// ❌ 现在顺序错了会编译错误!
// login(pass, user); // 类型不匹配!
login(user, pass); // ✅ 正确
}
newtype 的优势:
类型别名:给现有类型起个"小名",不是新类型。
// 长名字
type Result<T> = std::result::Result<T, std::io::Error>;
// 用起来
fn read_file() -> Result<String> {
// ...
}
类型别名 vs newtype:
特性 | 类型别名 | newtype |
|---|---|---|
类型检查 | 和原类型一样 | 独立类型 |
开销 | 零 | 零 |
添加方法 | 不能 | 能 |
用途 | 简化名字 | 类型安全 |
// 类型别名
type Meters = f64;
type Kilometers = f64;
fn main() {
let m: Meters = 1000.0;
let k: Kilometers = 1.0;
// ❌ 可以相加,但可能没意义
let sum = m + k; // 编译器不管
}
// newtype
struct Meters(f64);
struct Kilometers(f64);
fn main() {
let m = Meters(1000.0);
let k = Kilometers(1.0);
// ❌ 不能相加,类型不同
// let sum = m + k; // 编译错误!
}
never 类型:!,表示"永远不会返回"。
哪些函数返回 !?
panic!() - 程序崩溃loop 无限循环(没有 break)std::process::exit() - 退出程序fn crash() -> ! {
panic!("boom!");
}
fn forever() -> ! {
loop {
// 永远循环
}
}
never 类型的特点:
let guess: u32 = match some_result {
Ok(v) => v,
Err(_) => panic!("出错了!"), // panic! 返回 !,可以当 u32 用
};
为什么可以? 因为 ! 永远不会真正返回值,所以它可以假装是任何类型。
Sized 特质:标记类型在编译时是否有固定大小。
规则:
Sized 的(i32、String、结构体等)Sized 的(str、[T]、dyn Trait)// ❌ 不能这样
let s: str = "hello"; // str 不是 Sized
// ✅ 要这样
let s: &str = "hello"; // &str 是 Sized(引用有固定大小)
Sized 作为泛型约束:
// 泛型参数默认是 Sized 的
fn generic<T>(x: T) {} // 相当于 fn generic<T: Sized>(x: T) {}
// 如果不是 Sized,要显式标注
fn not_sized<T: ?Sized>(x: &T) {} // ?Sized 表示可以是也可以不是
?Sized 的含义:
T: Sized → T 必须是 Sized 的T: ?Sized → T 可以是 Sized 的,也可以不是// 可以接受 str 和 String
fn print_it<T: ?Sized>(x: &T)
where
T: std::fmt::Display
{
println!("{}", x);
}
fn main() {
print_it("hello"); // &str
print_it(&String::from("hello")); // &String
}
// 包装类型
struct Meters(f64);
struct Seconds(f64);
impl Meters {
fn new(value: f64) -> Self {
Meters(value)
}
fn to_kilometers(&self) -> Kilometers {
Kilometers(self. / 1000.0)
}
}
impl Seconds {
fn new(value: f64) -> Self {
Seconds(value)
}
fn to_minutes(&self) -> Minutes {
Minutes(self. / 60.0)
}
}
struct Kilometers(f64);
struct Minutes(f64);
fn main() {
let distance = Meters::new(1500.0);
let time = Seconds::new(120.0);
println!("距离:{:?} 米", distance.);
println!("距离:{:?} 公里", distance.to_kilometers().);
println!("时间:{:?} 秒", time.);
println!("时间:{:?} 分钟", time.to_minutes().);
// ❌ 不能混用
// let speed = distance.0 / time.0; // 类型不匹配
}
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self..join(", "))
}
}
fn main() {
let w = Wrapper(vec![
String::from("hello"),
String::from("world"),
]);
println!("w = {}", w); // 输出:w = [hello, world]
}
// 简化复杂类型
type Thunk = Box<dyn Fn() + Send + 'static>;
fn main() {
let f: Thunk = Box::new(|| {
println!("hello");
});
f();
}
// 带错误的 Result
type IoResult<T> = Result<T, std::io::Error>;
fn read_file(path: &str) -> IoResult<String> {
std::fs::read_to_string(path)
}
fn main() {
// match 表达式中的 never 类型
let result: Result<i32, &str> = Ok();
let value = match result {
Ok(v) => v,
Err(_) => panic!("出错了!"), // ! 可以当 i32 用
};
println!("值:{}", value);
// 无限循环
let mut count = ;
let x: i32 = loop {
count += ;
if count == {
break count * ; // 返回 i32
}
};
println!("x = {}", x);
}
use std::fmt::Display;
// 可以接受 Sized 和 ?Sized 类型
fn print_it<T: ?Sized>(x: &T)
where
T: Display
{
println!("{}", x);
}
// 只能接受 Sized 类型
fn print_sized<T: Display>(x: T) {
println!("{}", x);
}
fn main() {
// &str 不是 Sized,但 &(&str) 是
print_it("hello"); // ✅
// print_sized("hello"); // ✅ "hello" 是 &str,是 Sized 的
// dyn Trait 不是 Sized
let boxed: Box<dyn Display> = Box::new();
print_it(&*boxed); // ✅
// print_sized(*boxed); // ❌
}
// 用 newtype 防止 API 误用
struct Email(String);
struct Password(String);
impl Email {
fn new(email: &str) -> Result<Self, &'static str> {
if email.contains('@') {
Ok(Email(email.to_string()))
} else {
Err("无效的邮箱地址")
}
}
fn as_str(&self) -> &str {
&self.
}
}
impl Password {
fn new(password: &str) -> Result<Self, &'static str> {
if password.len() >= {
Ok(Password(password.to_string()))
} else {
Err("密码至少 8 位")
}
}
}
struct User {
email: Email,
password: Password,
}
fn main() {
let email = Email::new("larry@example.com").unwrap();
let password = Password::new("secure123").unwrap();
let user = User {
email,
password,
};
println!("用户邮箱:{}", user.email.as_str());
// ❌ 不能直接用字符串
// let bad_user = User {
// email: "bad@email", // 类型不匹配
// password: "123", // 类型不匹配
// };
}
// ❌ 错误理解
type Meters = f64;
type Kilometers = f64;
fn add_distance(a: Meters, b: Kilometers) -> f64 {
a + b // 可以相加,但语义上可能不对
}
// ✅ 正确:用 newtype
struct Meters2(f64);
struct Kilometers2(f64);
// fn add_distance2(a: Meters2, b: Kilometers2) -> ??? {
// a.0 + b.0 // 需要显式解包
// }
struct Wrapper(i32);
fn main() {
let w = Wrapper();
// ❌ 不能直接用
// let sum = w + 10;
// ✅ 要解包
let sum = w. + ;
}
fn main() {
// ❌ 不能创建 ! 类型的值
// let x: !;
// ✅ ! 只能作为返回值
fn crash() -> ! {
panic!();
}
}
fn print_it<T: ?Sized>(x: &T) {}
fn main() {
let s = "hello";
print_it(s); // ✅ s 是 &str
// ❌ 不能传值
// print_it("hello"); // 字面量是 &'static str
}
// 用 newtype 区分不同类型的 ID
struct UserId(u64);
struct PostId(u64);
struct CommentId(u64);
impl UserId {
fn new() -> Self {
UserId(rand::random())
}
}
struct User {
id: UserId,
name: String,
}
struct Post {
id: PostId,
author: UserId, // 明确是 UserId
content: String,
}
fn get_user_posts(user_id: UserId, posts: &[Post]) -> Vec<&Post> {
posts
.iter()
.filter(|p| p.author == user_id)
.collect()
}
fn main() {
let user = User {
id: UserId::new(),
name: String::from("Larry"),
};
let posts = vec![
Post {
id: PostId(),
author: user.id,
content: String::from("Hello"),
},
];
let user_posts = get_user_posts(user.id, &posts);
println!("用户有 {} 篇帖子", user_posts.len());
// ❌ 不能混用 ID 类型
// get_user_posts(PostId(1), &posts); // 编译错误!
}
// 用 newtype 实现类型安全的单位转换
#[derive(Debug, Clone, Copy)]
struct Meters(f64);
#[derive(Debug, Clone, Copy)]
struct Feet(f64);
impl Meters {
fn to_feet(&self) -> Feet {
Feet(self. * 3.28084)
}
}
impl Feet {
fn to_meters(&self) -> Meters {
Meters(self. / 3.28084)
}
}
// 实现加法(同单位)
impl std::ops::Add for Meters {
type Output = Meters;
fn add(self, other: Meters) -> Meters {
Meters(self. + other.)
}
}
fn main() {
let a = Meters(100.0);
let b = Meters(50.0);
let sum = a + b;
println!("总和:{:?} 米", sum);
let in_feet = sum.to_feet();
println!("总和:{:?} 英尺", in_feet);
// ❌ 不能直接相加不同单位
// let wrong = Meters(100.0) + Feet(50.0);
}
// 用 newtype 防止配置错误
struct Port(u16);
struct Host(String);
struct Timeout(std::time::Duration);
impl Port {
fn new(port: u16) -> Result<Self, &'static str> {
if port == || port > {
Err("无效端口")
} else {
Ok(Port(port))
}
}
}
struct DatabaseConfig {
host: Host,
port: Port,
timeout: Timeout,
}
fn connect(config: &DatabaseConfig) {
println!(
"连接到 {}:{},超时 {:?}",
config.host.,
config.port.,
config.timeout.
);
}
fn main() {
let config = DatabaseConfig {
host: Host(String::from("localhost")),
port: Port::new().unwrap(),
timeout: Timeout(std::time::Duration::from_secs()),
};
connect(&config);
// ❌ 类型安全防止错误
// let bad_config = DatabaseConfig {
// host: Host(String::from("localhost")),
// port: Port(99999), // 超出范围,但构造函数会检查
// timeout: Timeout(std::time::Duration::from_secs(30)),
// };
}

核心要点:
! 表示永不返回,可以转任何类型选择指南:
!?Sized下篇预告:
类型系统玩得差不多了,咱们来聊聊 Rust 里最让人头大的生命周期进阶。多个生命周期怎么搞?'static 是啥?trait 对象里的生命周期怎么处理?下篇一起攻克这个难关!
互动问题:
你觉得 newtype 模式有用吗?还是觉得太麻烦?有没有被 ?Sized 坑过?评论区聊聊!