第十五章:类型系统高级特性——类型级别的魔法
本章导读:如果说 Rust 的所有权系统是内存安全的基石,那么类型系统就是逻辑安全的护盾。Rust 的类型系统不仅能捕获类型错误,还能在编译期证明程序的正确性。本章我们将深入探索类型系统的高级特性,见证"让非法状态无法表示"的威力。
🎭 15.1 类型驱动开发
💡 15.1.1 核心思想
"让非法状态无法表示"(Make illegal states unrepresentable)是类型驱动开发的精髓。
// ❌ 不好的设计:允许非法状态
struct User {
email: String,
age: i32, // 可能是负数!
status: String, // 可能是任意字符串!
}
// ✅ 好的设计:非法状态无法表示
struct User {
email: Email, // 保证是有效的邮箱
age: Age, // 保证是非负数
status: UserStatus, // 只能是有效状态
}
// 新类型模式
#[derive(Debug)]
struct Email(String);
impl Email {
fn new(s: String) -> Result<Self, &'static str> {
if s.contains('@') {
Ok(Email(s))
} else {
Err("无效的邮箱地址")
}
}
}
#[derive(Debug)]
struct Age(u8); // 使用 u8,天然非负
impl Age {
fn new(value: u8) -> Result<Self, &'static str> {
if value > 0 && value < 150 {
Ok(Age(value))
} else {
Err("无效的年龄")
}
}
}
#[derive(Debug)]
enum UserStatus {
Active,
Inactive,
Suspended,
}
📦 15.2 新类型模式(Newtype Pattern)
🔒 15.2.1 类型安全
// 不同单位不应该混用
struct Meters(f64);
struct Feet(f64);
impl Meters {
fn to_feet(&self) -> Feet {
Feet(self.0 * 3.28084)
}
}
impl std::ops::Add for Meters {
type Output = Meters;
fn add(self, other: Meters) -> Meters {
Meters(self.0 + other.0)
}
}
fn main() {
let a = Meters(10.0);
let b = Meters(20.0);
let sum = a + b; // 正确:Meters + Meters = Meters
// let wrong = Meters(10.0) + Feet(32.8); // 编译错误!
}
🎯 15.2.2 零成本抽象
// 编译后与原始类型效率相同
struct Years(i64);
fn main() {
let age = Years(30);
println!("底层值: {}", age.0); // 直接访问
}
🌳 15.3 高阶类型(HKT)模拟
Rust 没有直接支持高阶类型,但可以通过技巧模拟。
📦 15.3.1 关联类型
trait Container {
type Item;
type Iter<'a>: Iterator<Item = &'a Self::Item>
where
Self: 'a;
fn iter(&self) -> Self::Iter<'_>;
}
impl<T> Container for Vec<T> {
type Item = T;
type Iter<'a> = std::slice::Iter<'a, T>;
fn iter(&self) -> Self::Iter<'_> {
self.as_slice().iter()
}
}
🔄 15.3.2 GAT(泛型关联类型)
trait LendingIterator {
type Item<'a> where Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>>;
}
// 实现:返回引用的迭代器
struct Windows<'a, T> {
slice: &'a [T],
size: usize,
pos: usize,
}
impl<'a, T> LendingIterator for Windows<'a, T> {
type Item<'b> = &'b [T] where Self: 'b;
fn next(&mut self) -> Option<Self::Item<'_>> {
if self.pos + self.size <= self.slice.len() {
let window = &self.slice[self.pos..self.pos + self.size];
self.pos += 1;
Some(window)
} else {
None
}
}
}
🎨 15.4 类型状态模式
使用类型系统编码对象的状态,在编译期防止状态错误。
🚦 15.4.1 基本实现
// 状态类型
struct Draft;
struct PendingReview;
struct Published;
// 文章,状态作为泛型参数
struct Post<State> {
title: String,
content: String,
state: State,
}
impl Post<Draft> {
fn new(title: String) -> Self {
Self {
title,
content: String::new(),
state: Draft,
}
}
fn add_content(mut self, content: String) -> Self {
self.content = content;
self
}
// 只有 Draft 状态可以提交审核
fn submit_for_review(self) -> Post<PendingReview> {
Post {
title: self.title,
content: self.content,
state: PendingReview,
}
}
}
impl Post<PendingReview> {
// 只有 PendingReview 状态可以批准
fn approve(self) -> Post<Published> {
Post {
title: self.title,
content: self.content,
state: Published,
}
}
// 拒绝,回到草稿
fn reject(self) -> Post<Draft> {
Post {
title: self.title,
content: self.content,
state: Draft,
}
}
}
impl Post<Published> {
// 只有 Published 状态可以获取内容
fn content(&self) -> &str {
&self.content
}
}
fn main() {
let draft = Post::new("标题".to_string());
let pending = draft.add_content("内容".to_string())
.submit_for_review();
let published = pending.approve();
println!("{}", published.content());
// 编译错误!Published 没有 submit_for_review 方法
// published.submit_for_review();
}
比喻:就像地铁闸机——只有刷卡后才能通过,编译器就是那个闸机,阻止你在错误状态下执行操作。
🔢 15.5 类型级编程
使用类型系统在编译期进行计算。
🔢 15.5.1 类型级数字
// 类型级自然数
struct Zero;
struct Succ<N>(std::marker::PhantomData<N>);
type One = Succ<Zero>;
type Two = Succ<One>;
type Three = Succ<Two>;
// 类型级加法
trait Add<Rhs> {
type Output;
}
impl<N> Add<Zero> for N {
type Output = N;
}
impl<N, M> Add<Succ<M>> for Succ<N>
where
N: Add<M>,
{
type Output = Succ<<N as Add<M>>::Output>;
}
// 编译期验证:One + Two = Three
fn assert_type_eq<T, U>()
where
T: std::any::Any<Associated = U> + ?Sized,
U: std::any::Any + ?Sized,
{
}
fn main() {
// 如果类型不匹配,编译失败
// assert_type_eq::<<One as Add<Two>>::Output, Three>();
}
✅ 15.5.2 类型级布尔
// 类型级布尔
struct True;
struct False;
// 类型级 Not
trait Not {
type Output;
}
impl Not for True {
type Output = False;
}
impl Not for False {
type Output = True;
}
// 类型级 And
trait And<Rhs> {
type Output;
}
impl And<True> for True {
type Output = True;
}
impl And<False> for True {
type Output = False;
}
impl<T> And<T> for False {
type Output = False;
}
🔒 15.6 幻影类型
使用不包含数据的类型参数来标记类型。
🏷️ 15.6.1 基本用法
use std::marker::PhantomData;
// 标记不同的单位
struct Unit;
struct Kilometer;
struct Mile;
struct Distance<UnitType> {
value: f64,
_marker: PhantomData<UnitType>,
}
impl Distance<Kilometer> {
fn from_km(value: f64) -> Self {
Self {
value,
_marker: PhantomData,
}
}
fn to_miles(&self) -> Distance<Mile> {
Distance {
value: self.value * 0.621371,
_marker: PhantomData,
}
}
}
impl Distance<Mile> {
fn from_miles(value: f64) -> Self {
Self {
value,
_marker: PhantomData,
}
}
fn to_km(&self) -> Distance<Kilometer> {
Distance {
value: self.value * 1.60934,
_marker: PhantomData,
}
}
}
fn main() {
let km = Distance::<Kilometer>::from_km(100.0);
let miles = km.to_miles();
let back = miles.to_km();
// 不能混合不同单位
// let wrong = km + miles; // 编译错误!
}
🛡️ 15.6.2 所有权标记
use std::marker::PhantomData;
struct Owned;
struct Borrowed;
struct CowString<Ownership> {
data: String,
_marker: PhantomData<Ownership>,
}
impl<Ownership> CowString<Ownership> {
fn as_str(&self) -> &str {
&self.data
}
}
impl CowString<Owned> {
fn new(s: String) -> Self {
Self {
data: s,
_marker: PhantomData,
}
}
fn into_borrowed(&self) -> CowString<Borrowed> {
CowString {
data: self.data.clone(),
_marker: PhantomData,
}
}
}
impl CowString<Borrowed> {
fn from_str(s: &str) -> Self {
Self {
data: s.to_string(),
_marker: PhantomData,
}
}
}
🎯 15.7 存在类型与 Trait 对象
📦 15.7.1 dyn Trait
trait Shape {
fn area(&self) -> f64;
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
struct Rectangle {
width: f64,
height: f64,
}
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
fn main() {
// 存在类型:隐藏具体类型,只暴露 trait
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Rectangle { width: 2.0, height: 3.0 }),
];
for shape in shapes {
println!("面积: {}", shape.area());
}
}
🔍 15.7.2 impl Trait
// 返回类型实现了某个 trait,但隐藏具体类型
fn create_shape(is_circle: bool) -> impl Shape {
if is_circle {
Circle { radius: 1.0 }
} else {
Rectangle { width: 2.0, height: 3.0 }
}
// 错误!impl Trait 要求单一具体类型
}
// 正确:单一返回类型
fn create_circle() -> impl Shape {
Circle { radius: 1.0 }
}
// 使用条件枚举
enum AnyShape {
Circle(Circle),
Rectangle(Rectangle),
}
impl Shape for AnyShape {
fn area(&self) -> f64 {
match self {
AnyShape::Circle(c) => c.area(),
AnyShape::Rectangle(r) => r.area(),
}
}
}
fn create_shape_v2(is_circle: bool) -> AnyShape {
if is_circle {
AnyShape::Circle(Circle { radius: 1.0 })
} else {
AnyShape::Rectangle(Rectangle { width: 2.0, height: 3.0 })
}
}
🧪 15.8 实战:类型安全的状态机
让我们实现一个网络连接的状态机:
use std::marker::PhantomData;
// 状态
struct Disconnected;
struct Connecting;
struct Connected;
struct Error;
// 连接事件
enum Event {
Connect,
Connected,
Disconnect,
Error,
Retry,
}
// 状态机
struct ConnectionState<State> {
address: String,
retry_count: u32,
_state: PhantomData<State>,
}
impl ConnectionState<Disconnected> {
fn new(address: String) -> Self {
Self {
address,
retry_count: 0,
_state: PhantomData,
}
}
fn connect(self) -> ConnectionState<Connecting> {
println!("正在连接 {}...", self.address);
ConnectionState {
address: self.address,
retry_count: self.retry_count,
_state: PhantomData,
}
}
}
impl ConnectionState<Connecting> {
fn on_connected(self) -> ConnectionState<Connected> {
println!("连接成功!");
ConnectionState {
address: self.address,
retry_count: 0,
_state: PhantomData,
}
}
fn on_error(self) -> ConnectionState<Error> {
println!("连接失败!");
ConnectionState {
address: self.address,
retry_count: self.retry_count,
_state: PhantomData,
}
}
}
impl ConnectionState<Connected> {
fn send(&self, data: &str) {
println!("发送到 {}: {}", self.address, data);
}
fn disconnect(self) -> ConnectionState<Disconnected> {
println!("断开连接");
ConnectionState {
address: self.address,
retry_count: 0,
_state: PhantomData,
}
}
}
impl ConnectionState<Error> {
fn retry(self) -> ConnectionState<Connecting> {
println!("重试连接(第 {} 次)...", self.retry_count + 1);
ConnectionState {
address: self.address,
retry_count: self.retry_count + 1,
_state: PhantomData,
}
}
fn give_up(self) -> ConnectionState<Disconnected> {
println!("放弃连接");
ConnectionState {
address: self.address,
retry_count: 0,
_state: PhantomData,
}
}
}
fn main() {
let initial = ConnectionState::<Disconnected>::new("192.168.1.1".to_string());
// 正确的状态转换
let connecting = initial.connect();
let connected = connecting.on_connected();
connected.send("Hello!");
// 编译错误:Disconnected 没有 send 方法
// initial.send("test");
// 错误处理
let disconnected = connected.disconnect();
let retry_connecting = disconnected.connect();
let error_state = retry_connecting.on_error();
let retry = error_state.retry();
let _connected = retry.on_connected();
}
📝 本章小结
本章我们探索了 Rust 类型系统的高级特性:
| 技术 | 用途 |
|---|---|
| 新类型模式 | 类型安全、零成本抽象 |
| 类型状态模式 | 编译期状态验证 |
| 幻影类型 | 类型标记 |
| 关联类型 | trait 关联类型 |
| GAT | 泛型关联类型 |
| 类型级编程 | 编译期计算 |
impl Trait | 返回类型抽象 |
核心思想:
- 让非法状态无法表示
- 利用类型系统在编译期捕获错误
- 类型是最好的文档
费曼技巧提问:为什么"让非法状态无法表示"比运行时检查更好?提示:想想 Bug 什么时候被发现成本最低。
动手实验:
- 使用新类型模式创建
Celsius和Fahrenheit类型,实现相互转换。- 实现一个类型安全的文件句柄:
File<Open>和File<Closed>,确保只能对打开的文件进行读写。- 使用幻影类型创建一个
String<Encoding>,区分 UTF-8 和 ASCII 字符串。- 解释为什么类型状态模式比使用
enum表示状态更安全。