1.Rust 特征(Trait)
Trait 类似于其他语言的接口 ,用于抽象共享行为,实现代码解耦与多态,是 Rust 最重要的语言特性之一。
一、什么是 Trait
- 定义一组方法签名,只规定 "能做什么",不规定**"怎么做"**
- 类型实现 Trait 后,就具备该 Trait 定义的行为
- 常用于泛型约束、统一接口、运算符重载、格式化打印等
定义 Trait
rust
pub trait Summary {
fn summarize(&self) -> String;
}
为类型实现 Trait
rust
pub struct Post {
pub title: String,
pub author: String,
}
impl Summary for Post {
fn summarize(&self) -> String {
format!("文章《{}》, 作者{}", self.title, self.author)
}
}
二、关键知识点 + 代码示例
1. 孤儿规则(重要)
要为类型 A 实现 Trait T,必须满足:
- A 或 T 至少有一个在当前包定义
- 不能为外部类型实现外部 Trait(避免冲突破坏代码)
2. 默认实现
Trait 可以提供默认方法,实现类可直接用或重写。
rust
pub trait Summary {
fn summarize(&self) -> String {
String::from("(阅读更多...)")
}
}
// 使用默认实现
impl Summary for Post {}
默认实现还能调用 Trait 内其他方法:
rust
pub trait Summary {
fn author(&self) -> String;
fn summarize(&self) -> String {
format!("来自 {} 的文章", self.author())
}
}
3. Trait 作函数参数(impl Trait)
rust
pub fn notify(item: &impl Summary) {
println!("{}", item.summarize());
}
- 任何实现了
Summary的类型都可传入
4. 泛型约束(Trait Bound)
impl Trait 是语法糖,完整写法:
rust
pub fn notify<T: Summary>(item: &T) {
println!("{}", item.summarize());
}
强制两个参数同类型只能用约束:
rust
pub fn notify<T: Summary>(a: &T, b: &T) {}
5. 多重约束
rust
fn notify(item: &(impl Summary + Display)) {}
// 或
fn notify<T: Summary + Display>(item: &T) {}
6. where 简化复杂约束
rust
fn some_fn<T, U>(t: &T, u: &U)
where
T: Display + Clone,
U: Clone + Debug,
{
// ...
}
7. 有条件实现方法
只有满足约束的类型才拥有该方法:
rust
struct Pair<T> { x: T, y: T }
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
// 比较并打印
}
}
8. 返回 impl Trait
适合返回复杂类型(迭代器、闭包):
rust
fn get_summarizable() -> impl Summary {
Post { ... }
}
⚠️ 限制:不能分支返回不同类型,否则编译报错。
9. 修复泛型比较(最经典示例)
rust
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
PartialOrd:支持比较Copy:避免所有权转移问题
10. derive 派生特征
自动实现标准 Trait:
rust
#[derive(Debug, Clone, Copy)]
struct Point { x: i32, y: i32 }
常用:Debug、Clone、Copy、PartialEq、Default
11. 使用 Trait 必须先引入
rust
use std::convert::TryInto;
let a: u8 = 10.try_into().unwrap();
三、典型实战示例
1)自定义类型支持 + 运算符
rust
use std::ops::Add;
#[derive(Debug)]
struct Point {
x: f32,
y: f32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
// p1 + p2 可用
2)实现 Display 自定义打印
rust
use std::fmt;
struct File {
name: String,
}
impl fmt::Display for File {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "文件: {}", self.name)
}
}
// println!("{}", file);
四、全文核心一句话
Trait = 行为抽象 + 接口约束 + 多态基础 ,配合泛型使用,是 Rust 实现零成本抽象、代码复用、类型安全的核心机制。
2.Rust 特征对象(Trait Object)
特征对象用于在同一集合 / 返回值中存放不同类型,但都实现同一个 Trait ,实现 Rust 版 "多态",解决 impl Trait 只能返回单一类型的问题。
1. 为什么需要特征对象
impl Trait只能返回一种具体类型,不能分支返回多种类型- 需要用同一个列表 存放
Button、SelectBox等不同 UI 组件 - 不想用枚举(枚举需要预先定义所有类型,扩展性差)
典型报错(impl Trait 不能多类型返回):
rust
fn returns_summarizable(switch: bool) -> impl Summary {
if switch { Post { ... } } // 类型1
else { Weibo { ... } } // 类型2 → 编译报错
}
2. 特征对象定义与写法
语法:
&dyn TraitBox<dyn Trait>(最常用,存到堆上)
dyn 表明这是动态分发的特征对象。
最小示例
rust
trait Draw {
fn draw(&self) -> String;
}
// 为不同类型实现同一个 Trait
impl Draw for u8 {
fn draw(&self) -> String { format!("u8: {}", self) }
}
impl Draw for f64 {
fn draw(&self) -> String { format!("f64: {}", self) }
}
// 特征对象作为参数
fn draw(x: &dyn Draw) {
println!("{}", x.draw());
}
fn main() {
draw(&8u8); // 整数
draw(&1.1); // 浮点数
}
3. 经典实战:UI 组件渲染
rust
trait Draw {
fn draw(&self);
}
// 不同组件
struct Button { label: String }
struct SelectBox { options: Vec<String> }
impl Draw for Button {
fn draw(&self) { println!("绘制按钮:{}", self.label); }
}
impl Draw for SelectBox {
fn draw(&self) { println!("绘制选择框"); }
}
// 统一存放不同组件
struct Screen {
components: Vec<Box<dyn Draw>>,
}
impl Screen {
fn run(&self) {
for c in &self.components {
c.draw(); // 统一调用
}
}
}
fn main() {
let screen = Screen {
components: vec![
Box::new(Button { label: "OK".into() }),
Box::new(SelectBox { options: vec!["A".into()] }),
],
};
screen.run();
}
4. 静态分发 vs 动态分发
| 方式 | 写法 | 原理 | 性能 | 适用场景 |
|---|---|---|---|---|
| 静态分发 | T: Trait |
编译期单态化 | 快,无开销 | 同构集合、高性能 |
| 动态分发 | dyn Trait |
运行时查 vtable | 略低 | 异构集合、多态 |
5. 特征对象内存结构(关键点)
Box<dyn Trait> / &dyn Trait 占两个指针大小:
- ptr:指向实际类型实例(数据)
- vptr :指向 vtable(虚表),存储方法地址
运行时通过 vtable 找到对应方法 → 动态分发
6. 对象安全(Object Safety)------ 必考重点
只有对象安全的 Trait 才能做成特征对象。
满足两条:
- 方法不能返回
Self - 方法不能带泛型参数
反例:Clone 不安全,因为返回 Self
rust
pub trait Clone {
fn clone(&self) -> Self; // 返回 Self → 对象不安全
}
下面代码编译失败:
rust
let components: Vec<Box<dyn Clone>>; // ❌ 报错
7. Self vs self
self:当前实例的引用Self:当前实现 Trait 的具体类型
rust
trait Draw {
fn draw(&self) -> Self;
}
struct Button;
impl Draw for Button {
fn draw(&self) -> Self { Button } // Self = Button
}
8. 重要规则补充
-
特征对象本身无固定大小
→ 必须用 Box 包裹
rustfn f(x: dyn Draw) {} // ❌ 编译错误 -
特征对象只能调用 Trait 中定义的方法,不能调用类型独有方法
-
孤儿规则同样适用:外部类型不能实现外部 Trait
一句话记忆
dyn Trait = 运行时多态 + 异构集合 + vtable 动态分发,必须对象安全。
3. 其他
重点讲解关联类型、默认泛型参数、同名方法调用、基特征、newtype 绕过孤儿规则等核心高级用法。
1. 关联类型(Associated Types)
在 trait 内部定义的类型,用于提高代码可读性、简化泛型使用。
rust
pub trait Iterator {
type Item; // 关联类型
fn next(&mut self) -> Option<Self::Item>;
}
struct Counter;
impl Iterator for Counter {
type Item = u32; // 具体指定类型
fn next(&mut self) -> Option<Self::Item> {
Some(0)
}
}
对比泛型
- 泛型:
Iterator<Item>每次都要写明类型 - 关联类型:
Iterator更简洁,适合一个类型只对应一种输出的场景
关联类型也可加约束:
rust
trait Container {
type A: Display;
}
2. 默认泛型类型参数
给泛型指定默认类型 ,简化写法,典型如 Add<RHS=Self>。
rust
use std::ops::Add;
struct Point { x: i32, y: i32 }
impl Add for Point { // 使用默认 RHS=Self
type Output = Point;
fn add(self, other: Self) -> Self {
Point { x: self.x + other.x, y: self.y + other.y }
}
}
不同类型相加(显式指定 RHS):
rust
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + other.0 * 1000)
}
}
3. 同名方法调用
一个类型可能实现多个 trait,且出现同名方法,需区分调用。
有 &self 的方法
rust
trait Pilot { fn fly(&self); }
trait Wizard { fn fly(&self); }
struct Human;
impl Pilot for Human { fn fly(&self) { println!("captain"); } }
impl Wizard for Human { fn fly(&self) { println!("up"); } }
impl Human { fn fly(&self) { println!("waving"); } }
// 调用
let person = Human;
Pilot::fly(&person); // 显式指定 trait
Wizard::fly(&person);
person.fly(); // 默认调用类型自身方法
无 self 的关联函数
必须用完全限定语法:
rust
trait Animal { fn baby_name() -> String; }
struct Dog;
impl Dog { fn baby_name() -> String { "Spot".into() } }
impl Animal for Dog { fn baby_name() -> String { "puppy".into() } }
// 必须这样调用
<Dog as Animal>::baby_name();
完全限定语法通用形式:
<Type as Trait>::function(args...)
4. 基特征(Super Trait)
trait 依赖另一个 trait,要求实现该 trait 必须先实现依赖 trait。
rust
use std::fmt::Display;
// OutlinePrint 要求必须实现 Display
trait OutlinePrint: Display {
fn outline_print(&self) {
let s = self.to_string();
println!("*{:?}*", s);
}
}
struct Point(i32, i32);
impl Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.0, self.1)
}
}
impl OutlinePrint for Point {} // 现在可以实现
5. newtype 模式(绕过孤儿规则)
孤儿规则:不能为外部类型实现外部 trait。
用元组结构体包裹外部类型,即可实现任意外部 trait。
rust
use std::fmt;
// 包装 Vec<String>
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
// 使用
let w = Wrapper(vec!["hello".into(), "world".into()]);
println!("{}", w);
缺点:不能直接调用内部类型方法,可通过 Deref 解决。
核心一句话记忆
- 关联类型:让 trait 更简洁
- 默认泛型:简化重复声明
- 同名方法 :用
<Type as Trait>::精确调用 - 基特征:trait 之间的依赖约束
- newtype:包装外部类型,绕开孤儿规则