Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust Trait:用共享行为连接不同类型

Trait 描述一组类型必须提供的行为,作用接近其他语言的接口,但 Rust 还可以通过默认实现、泛型约束和 trait object 支持静态与动态分发。

1. 定义并实现 Trait

下面的 Describe 要求类型提供 describe 方法。Teacher 和 Student 的字段不同,但都可以被同一个泛型函数使用:

trait Describe {
    fn describe(&self) -> String;
}

struct Teacher {
    name: String,
    subject: String,
}

struct Student {
    name: String,
    grade: u8,
}

impl Describe for Teacher {
    fn describe(&self) -> String {
        format!("教师 {},教授 {}", self.name, self.subject)
    }
}

impl Describe for Student {
    fn describe(&self) -> String {
        format!("学生 {},年级 {}", self.name, self.grade)
    }
}

fn print_description<T: Describe>(value: &T) {
    println!("{}", value.describe());
}

fn main() {
    let teacher = Teacher { name: "Lin".into(), subject: "Rust".into() };
    let student = Student { name: "Yu".into(), grade: 2 };
    print_description(&teacher);
    print_description(&student);
}

T: Describe 是编译期约束。编译器会为每种具体类型生成调用路径,通常可以内联,称为静态分发。

2. impl Trait 与 dyn Trait

参数位置可以写成 impl Trait:

#![allow(unused)]
fn main() {
fn print_description(value: &impl Describe) {
    println!("{}", value.describe());
}
}

它表达“传入某个实现了 Trait 的具体类型”,仍然是静态分发。等价的泛型写法 fn print_description<T: Describe>(value: &T) 更适合同时约束多个参数为同一种类型。

如果需要在同一个集合中保存不同的实现,使用 trait object:

#![allow(unused)]
fn main() {
let people: Vec<Box<dyn Describe>> = vec![
    Box::new(Teacher { name: "Lin".into(), subject: "Rust".into() }),
    Box::new(Student { name: "Yu".into(), grade: 2 }),
];

for person in people {
    println!("{}", person.describe());
}
}

dyn Describe 通过虚表在运行时选择实现,属于动态分发。Trait 必须满足 object safety 才能作为 trait object;带有返回 Self 或泛型方法的 Trait 通常不能直接用于 dyn Trait。

3. 默认实现与 Trait bound

Trait 可以提供默认实现,具体类型只覆盖真正不同的行为:

#![allow(unused)]
fn main() {
trait Summary {
    fn title(&self) -> &str;

    fn preview(&self) -> String {
        format!("摘要:{}", self.title())
    }
}

fn show<T: Summary>(item: &T) {
    println!("{}", item.preview());
}
}

多个约束可以使用 + 组合,例如 T: Summary + Display。返回值位置可以写 impl Summary,但一个函数只能返回一种隐藏的具体类型;如果需要返回多种实现,应返回 Box<dyn Summary>。

4. 常见误区

  • Trait 不会自动为类型实现行为,必须写出 impl Trait for Type。
  • impl Trait 通常表示一个具体类型;dyn Trait 才表示运行时可替换的多种类型。
  • 方法命名应遵循 Rust 风格,例如 say_age 和 self_introduction,不要使用 Sayage、SelfIntroduction 这类大驼峰方法名。
  • 只有在确实需要异构集合或运行时插件时才使用 Box<dyn Trait>;普通参数优先使用泛型约束。