高级类型
type 类型别名
Rust 提供了声明类型别名(type alias)的能力,使用 type 关键字为现有类型赋予另一个名字.
定义语法
语法: type NewName = ExistingType;
type Kilometers = i32;这意味着 Kilometers 是 i32 的类型同义词(synonym).你可以使用 Kilometers 来声明变量、函数参数或返回类型,就像使用 i32 一样.Kilometers 类型的值将被完全当作 i32 类型值来对待:
type Kilometers = i32;
let x: i32 = 5;
let y: Kilometers = 5;
println!("x + y = {}", x + y);因为 Kilometers 是 i32 的别名,它们是同一类型,可以将 i32 与 Kilometers 相加,也可以将 Kilometers 传递给接收 i32 参数的函数.但通过这种手段无法获得 newtype 模式所提供的类型检查的好处.换句话说,混用 Kilometers 和 i32 的值时,编译器不会报错.
减少重复声明
类型别名的主要用途是减少重复声明,特别是当你有复杂的类型时.
// 定义一个复杂的类型别名
type Thunk = Box<dyn Fn() + Send + 'static>;
// 使用类型别名来声明一个变量
let f: Thunk = Box::new(|| println!("hi"));Result<T> 案例
Rust 标准库中定义了 Result<T, E> 枚举类型,用于表示可能成功(Ok)或失败(Err)的操作结果.对于许多函数来说,错误类型 E 通常是相同的,因此你可以使用类型别名来简化代码:
// 定义一个错误类型
type MyResult<T> = Result<T, MyError>;
// 使用类型别名来简化函数返回类型
fn do_something() -> MyResult<()> {
// ...
}例如,std::io::Result<T> 是 Result<T, std::io::Error> 的类型别名.不使用类型别名需要这样写:
use std::fmt;
use std::io::Error;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
fn flush(&mut self) -> Result<(), Error>;
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}在标准库中,std::io 模块定义了类型别名 type Result<T> = std::result::Result<T, std::io::Error>; 来简化代码:
use std::io::Result;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, buf: &[u8]) -> Result<()>;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()>;
}注意:因为 Rust 默认引入
std::result::Result,如果你写了use std::io::Result;,在该文件中Result<T>就会变成只带一个参数的别名,导致无法直接使用原生的Result<T, E>.
! Never 类型
发散函数
发散函数(Diverging Functions)是指那些永远不会返回的函数.它们通常表现为:
- 陷入无限循环(
loop) - 触发
panic!() - 调用
std::process::exit()
! 类型
Rust 使用特殊的 ! 类型(Never 类型)来表示发散函数的返回结果.
- 核心作用:明确标记该代码路径永远不会产生结果
- 万能适配:
!类型可以被强制转换为任意其他类型
代码示例
示例 1:显式定义发散函数
fn forever() -> ! {
loop {
println!("无限循环,永不返回");
}
}示例 2:在 match 表达式中使用
由于 ! 能适配任何类型,你可以在任何需要返回值的地方使用 panic!():
fn divide(a: i32, b: i32) -> i32 {
match b {
0 => panic!("除数不能为零"), // panic! 返回 !,自动适配 i32
_ => a / b,
}
}常见的 ! 类型表达式
- 程序中断:
panic!() - 控制流:
loop {}、continue、break - 程序退出:
std::process::exit() - 占位占坑:
todo!()、unimplemented!()
这些表达式都具有
!类型,可以在任何需要类型的地方使用.
动态大小类型和 Sized trait
在 Rust 中,编译器必须知道每个变量占多少字节.但有些数据的大小只能在运行时才能确定,这就是动态大小类型(DST,Dynamically Sized Type)的问题.
痛点:有些数据的大小在编译时无法确定,必须等程序运行时才知道.例如 str——一段文字到底是 5 个字还是 500 个字?既然不知道大小,编译器就无法为其预分配内存.
动态大小类型 (DST)
- 定义:在编译时无法确定大小,只能在运行时确定大小的类型(如
str、[T]、dyn Trait) - 局限:不能直接创建 DST 类型的变量,也不能将其作为函数参数直接传递
- 原则:Rust 要求同类型的所有实例必须占用相同大小的空间
let s1: str = "Hello there!"; // 错误:12 字节
let s2: str = "How's it going?"; // 错误:15 字节这两个 str 大小不同,所以不能直接创建 str 类型的变量.
指针后的 DST
DST 必须置于指针之后(如 &T、Box<T>、Rc<T>).指向 DST 的指针通常包含两部分信息:
- 数据的内存地址
- 元信息(如字符串长度、slice 长度或 trait 对象的虚表)
这被称为胖指针(Fat Pointer).
let s1: &str = "Hello there!"; // &str 大小固定(16 字节)
let s2: &str = "How's it going?"; // &str 大小固定(16 字节)此时 s1 和 s2 都是 &str 类型,大小固定,编译器可以分配内存.
Sized Trait
为了处理 DST,Rust 提供了 Sized trait 来决定一个类型的大小是否在编译时可知.该 trait 会自动为所有在编译时大小已知的类型实现.
- 作用:标记一个类型的大小在编译时是否可知
- 自动实现:Rust 为所有编译时大小已知的类型自动实现
Sized - 隐式约束:所有泛型函数默认都带有
T: Sized约束
fn func<T>(t: T) {}
// 等同于 fn func<T: Sized>(t: T) {}?Sized 语法
- 含义:表示"可能不是
Sized"(T 可以是固定大小或动态大小) - 用途:放宽泛型约束,使其能接收 DST 类型
- 要求:参数类型通常必须改为指针形式(如
&T)
fn func<T: ?Sized>(t: &T) {}
// 此时 T 可以是 str 或 dyn Trait