Trait 与泛型
Trait vs 鸭子类型
Section titled “Trait vs 鸭子类型”学习内容: Trait 作为显式契约(vs Python 鸭子类型)、
Protocol(PEP 544)≈ Trait、 带where子句的泛型约束、trait 对象(dyn Trait)vs 静态分发,以及常用标准 trait。难度: 🟡 中级
这是 Rust 类型系统真正让 Python 开发者眼前一亮的地方。 Python 的”鸭子类型”说:“如果它走路像鸭子,叫声像鸭子,它就是鸭子。” Rust 的 trait 说:“我会精确告诉你我需要什么鸭子行为,在编译期。“
Python 鸭子类型
Section titled “Python 鸭子类型”# Python — 鸭子类型:有正确方法的任何东西都能用def total_area(shapes): """适用于任何有 .area() 方法的东西。""" return sum(shape.area() for shape in shapes)
class Circle: def __init__(self, radius): self.radius = radius def area(self): return 3.14159 * self.radius ** 2
class Rectangle: def __init__(self, w, h): self.w, self.h = w, h def area(self): return self.w * self.h
# 运行时工作 — 不需要继承!shapes = [Circle(5), Rectangle(3, 4)]print(total_area(shapes)) # 90.54
# 但如果没有 .area() 呢?class Dog: def bark(self): return "Woof!"
total_area([Dog()]) # 运行时 AttributeError: 'Dog' has no attribute 'area'# 错误发生在运行时,不是在定义时Rust Trait——显式鸭子类型
Section titled “Rust Trait——显式鸭子类型”// Rust — trait 使"鸭子"契约显式trait HasArea { fn area(&self) -> f64; # 实现这个 trait 的任何类型都有 .area()}
struct Circle { radius: f64 }struct Rectangle { width: f64, height: f64 }
impl HasArea for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }}
impl HasArea for Rectangle { fn area(&self) -> f64 { self.width * self.height }}
// trait 约束是显式的 — 编译器在编译期检查fn total_area(shapes: &[&dyn HasArea]) -> f64 { shapes.iter().map(|s| s.area()).sum()}
// 使用:let shapes: Vec<&dyn HasArea> = vec![&Circle { radius: 5.0 }, &Rectangle { width: 3.0, height: 4.0 }];println!("{}", total_area(&shapes)); // 90.54
// struct Dog;// total_area(&[&Dog {}]); // 编译错误:Dog 没有实现 HasArea核心洞见:Python 的鸭子类型把错误推迟到运行时。Rust 的 trait 在编译期捕获。 同样的灵活性,更早的错误检测。
Protocol(PEP 544)vs Trait
Section titled “Protocol(PEP 544)vs Trait”Python 3.8 引入了 Protocol(PEP 544)用于结构化子类型——这是最接近 Rust trait 的 Python 概念。
Python Protocol
Section titled “Python Protocol”# Python — Protocol(结构化类型,类似 Rust trait)from typing import Protocol, runtime_checkable
@runtime_checkableclass Printable(Protocol): def to_string(self) -> str: ...
class User: def __init__(self, name: str): self.name = name def to_string(self) -> str: return f"User({self.name})"
class Product: def __init__(self, name: str, price: float): self.name = name self.price = price def to_string(self) -> str: return f"Product({self.name}, ${self.price:.2f})"
def print_all(items: list[Printable]) -> None: for item in items: print(item.to_string())
# 可以工作,因为 User 和 Product 都有 to_string()print_all([User("Alice"), Product("Widget", 9.99)])
# 但:mypy 检查这个,Python 运行时 不强制# print_all([42]) # mypy 警告,但 Python 运行并崩溃Rust Trait(等价,但强制执行!)
Section titled “Rust Trait(等价,但强制执行!)”// Rust — trait 在编译期强制执行trait Printable { fn to_string(&self) -> String;}
struct User { name: String }struct Product { name: String, price: f64 }
impl Printable for User { fn to_string(&self) -> String { format!("User({})", self.name) }}
impl Printable for Product { fn to_string(&self) -> String { format!("Product({}, ${:.2})", self.name, self.price) }}
fn print_all(items: &[&dyn Printable]) { for item in items { println!("{}", item.to_string()); }}
// print_all(&[&42i32]); // 编译错误:i32 没有实现 Printable| 特性 | Python Protocol | Rust Trait |
|---|---|---|
| 结构化类型 | ✅(隐式) | ❌(显式 impl) |
| 检查时机 | 运行时(或 mypy) | 编译期(总是) |
| 默认实现 | ❌ | ✅ |
| 可添加到外部类型 | ❌ | ✅(有限制) |
| 多 protocol | ✅ | ✅(多 trait) |
| 关联类型 | ❌ | ✅ |
| 泛型约束 | ✅(用 TypeVar) | ✅(trait bound) |
Python 泛型
Section titled “Python 泛型”# Python — TypeVar 用于泛型函数from typing import TypeVar, Sequence
T = TypeVar('T')
def first(items: Sequence[T]) -> T | None: return items[0] if items else None
# 有界的 TypeVarfrom typing import SupportsFloatT = TypeVar('T', bound=SupportsFloat)
def average(items: Sequence[T]) -> float: return sum(float(x) for x in items) / len(items)Rust 带 Trait Bound 的泛型
Section titled “Rust 带 Trait Bound 的泛型”// Rust — 带 trait bound 的泛型fn first<T>(items: &[T]) -> Option<&T> { items.first()}
// 带 trait bound — "T 必须实现这些 trait"fn average<T>(items: &[T]) -> f64where T: Into<f64> + Copy, // T 必须能转换为 f64 且可复制{ let sum: f64 = items.iter().map(|&x| x.into()).sum(); sum / items.len() as f64}
// 多重约束 — "T 必须实现 Display AND Debug AND Clone"fn log_and_clone<T: std::fmt::Display + std::fmt::Debug + Clone>(item: &T) -> T { println!("Display: {}", item); println!("Debug: {:?}", item); item.clone()}
// 简写形式用 impl Trait(简单情况)fn print_it(item: &impl std::fmt::Display) { println!("{}", item);}泛型快速参考
Section titled “泛型快速参考”| Python | Rust | 备注 |
|---|---|---|
TypeVar('T') | <T> | 无界泛型 |
TypeVar('T', bound=X) | <T: X> | 有界泛型 |
Union[int, str] | 枚举或 trait 对象 | Rust 没有联合类型 |
Sequence[T] | &[T](切片) | 借用的序列 |
Callable[[A], R] | Fn(A) -> R | 函数 trait |
Optional[T] | Option<T> | 语言内置 |
常用标准 Library Trait
Section titled “常用标准 Library Trait”这些是 Rust 版本的 Python”魔术方法”——它们定义类型在常见场景下的行为。
Display 和 Debug(打印)
Section titled “Display 和 Debug(打印)”use std::fmt;
// Debug — 类似 __repr__(可派生)#[derive(Debug)]struct Point { x: f64, y: f64 }// 现在可以:println!("{:?}", point);
// Display — 类似 __str__(必须手动实现)impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) }}// 现在可以:println!("{}", point);比较 Trait
Section titled “比较 Trait”// PartialEq — 类似 __eq__// Eq — 完全相等(f64 是 PartialEq 但不是 Eq,因为 NaN != NaN)// PartialOrd — 类似 __lt__、__le__ 等// Ord — 完全排序
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]struct Student { name: String, grade: i32,}
// 现在 Student 可以:比较、排序、用作 HashMap 键、克隆let mut students = vec![ Student { name: "Charlie".into(), grade: 85 }, Student { name: "Alice".into(), grade: 92 },];students.sort(); // 使用 Ord — 按 name 然后 grade 排序(结构体字段顺序)Iterator Trait
Section titled “Iterator Trait”// 实现 Iterator — 类似 Python 的 __iter__/__next__struct Countdown { value: i32 }
impl Iterator for Countdown { type Item = i32; // 迭代器产生什么类型
fn next(&mut self) -> Option<Self::Item> { if self.value > 0 { self.value -= 1; Some(self.value + 1) } else { None // 迭代完成 } }}
// 使用:for n in (Countdown { value: 5 }) { println!("{n}"); // 5, 4, 3, 2, 1}常用 Trait 一览
Section titled “常用 Trait 一览”| Rust Trait | Python 等价物 | 用途 |
|---|---|---|
Display | __str__ | 人类可读字符串 |
Debug | __repr__ | 调试字符串(可派生) |
Clone | copy.deepcopy | 深复制 |
Copy | (int/float 自动复制) | 简单类型的隐式复制 |
PartialEq / Eq | __eq__ | 相等比较 |
PartialOrd / Ord | __lt__ 等 | 排序 |
Hash | __hash__ | 可哈希(用于 dict 键) |
Default | 默认 __init__ | 默认值 |
From / Into | __init__ 重载 | 类型转换 |
Iterator | __iter__ / __next__ | 迭代 |
Drop | __del__ / __exit__ | 清理 |
Add, Sub, Mul | __add__, __sub__, __mul__ | 运算符重载 |
Index | __getitem__ | 用 [] 索引 |
Deref | (无等价物) | 智能指针解引用 |
Send / Sync | (无等价物) | 线程安全标记 |
flowchart TB subgraph Static ["Static Dispatch (impl Trait)"] G["fn notify(item: &impl Summary)"] --> M1["Compiled: notify_Article()"] G --> M2["Compiled: notify_Tweet()"] M1 --> O1["Inlined, zero-cost"] M2 --> O2["Inlined, zero-cost"] end subgraph Dynamic ["Dynamic Dispatch (dyn Trait)"] D["fn notify(item: &dyn Summary)"] --> VT["vtable lookup"] VT --> I1["Article::summarize()"] VT --> I2["Tweet::summarize()"] end style Static fill:#d4edda style Dynamic fill:#fff3cdPython 等价物:Python 总是使用动态分发(运行时
getattr)。 Rust 默认使用静态分发(单态化——编译器为每个具体类型生成专用代码)。 只有当你需要运行时多态时才使用dyn Trait。另请参阅:第 11 章 — From/Into Trait 深入讲解了转换 trait(
From、Into、TryFrom)。
Rust trait 可以定义关联类型——每个实现者填充的类型占位符。Python 没有等价物:
// Iterator 定义了关联类型 'Item'trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>;}
struct Countdown { remaining: u32 }
impl Iterator for Countdown { type Item = u32; # 这个迭代器产生 u32 值 fn next(&mut self) -> Option<u32> { if self.remaining > 0 { self.remaining -= 1; Some(self.remaining) } else { None } }}在 Python 中,__iter__ / __next__ 返回 Any——没有办法声明”这个迭代器产生 int”并让它被强制执行(带 Iterator[int] 的类型提示只是建议性的)。
运算符重载:__add__ → impl Add
Section titled “运算符重载:__add__ → impl Add”Python 使用魔术方法(__add__、__mul__)。Rust 使用 trait 实现——同样的思路,但编译期类型检查:
# Pythonclass Vec2: def __init__(self, x, y): self.x, self.y = x, y def __add__(self, other): return Vec2(self.x + other.x, self.y + other.y) # 对 'other' 没有类型检查use std::ops::Add;
#[derive(Debug, Clone, Copy)]struct Vec2 { x: f64, y: f64 }
impl Add for Vec2 { type Output = Vec2; # 关联类型:+ 返回什么? fn add(self, rhs: Vec2) -> Vec2 { Vec2 { x: self.x + rhs.x, y: self.y + rhs.y } }}
let a = Vec2 { x: 1.0, y: 2.0 };let b = Vec2 { x: 3.0, y: 4.0 };let c = a + b; # 类型安全:只允许 Vec2 + Vec2关键区别:Python 的 __add__ 在运行时接受任何 other(你必须手动检查类型,否则得到 TypeError)。
Rust 的 Add trait 在编译期强制操作数类型——Vec2 + i32 是编译错误,
除非你显式地为 Vec2 实现 impl Add<i32>。
练习:泛型 Summary Trait(点击展开)
挑战:定义 trait Summary,方法 fn summarize(&self) -> String。
为两个结构体实现它:Article { title: String, body: String } 和 Tweet { username: String, content: String }。
然后编写函数 fn notify(item: &impl Summary) 打印摘要。
答案
trait Summary { fn summarize(&self) -> String;}
struct Article { title: String, body: String }struct Tweet { username: String, content: String }
impl Summary for Article { fn summarize(&self) -> String { format!("{} — {}...", self.title, &self.body[..20.min(self.body.len())]) }}
impl Summary for Tweet { fn summarize(&self) -> String { format!("@{}: {}", self.username, self.content) }}
fn notify(item: &impl Summary) { println!("📢 {}", item.summarize());}
fn main() { let article = Article { title: "Rust is great".into(), body: "Here is why Rust beats Python for systems...".into(), }; let tweet = Tweet { username: "rustacean".into(), content: "Just shipped my first crate!".into(), }; notify(&article); notify(&tweet);}关键收获:&impl Summary 是 Rust 等价于带 summarize 方法的 Python Protocol。
但 Rust 在编译期检查——传递不实现 Summary 的类型是编译错误,
不是运行时的 AttributeError。