ToolkitX
知识库工具箱

Trait 与泛型

trait 定义、泛型约束、trait 对象

25min·高级

01. Trait 定义和实现

Trait 定义共享行为,类似其他语言的接口。用 trait 关键字定义方法签名,用 impl Xxx for YourType 为你的类型实现 trait。可以在 trait 里提供默认实现,实现者可以复用或覆盖。孤儿规则:不能为外部类型实现外部 trait,防止冲突。
rust
trait Summary {
    fn summarize(&self) -> String;
    
    // 默认实现
    fn summarize_default(&self) -> String {
        String::from("(摘要暂无)")
    }
}

struct Article {
    title: String,
    content: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{} - {}", self.title, &self.content[..20])
    }
}

let article = Article {
    title: "Rust 入门".into(),
    content: "这是一篇关于 Rust...".into(),
};
println!("{}", article.summarize());
Rust 的 trait 类似 Go 的 interface 但需要显式实现(impl Trait for Type),编译器帮你检查。

02. Trait 作为参数

函数参数可以用 impl Trait 语法接受实现了指定 trait 的类型,这是语法糖,底层还是泛型。Trait Bound 用 T: Trait 约束泛型参数。+ 号组合多个 trait 条件(T: Summary + Display)。where 子句在复杂约束时让代码更可读。
rust
// impl Trait 语法
fn notify(item: &impl Summary) {
    println!("通知: {}", item.summarize());
}

// 等价泛型写法
fn notify_generic<T: Summary>(item: &T) {
    println!("通知: {}", item.summarize());
}

// 多个 trait 约束
fn display_and_summarize<T: Summary + std::fmt::Display>(item: &T) {
    println!("显示: {}  摘要: {}", item, item.summarize());
}

// where 子句(复杂情况更清晰)
fn complex<T, U>(t: &T, u: &U) -> String
where
    T: Summary + Clone,
    U: Clone + std::fmt::Debug,
{
    format!("{} {:?}", t.summarize(), u)
}
函数签名简单用 impl Trait,多个参数各需不同 trait 用泛型 + where。

03. 返回实现了 Trait 的类型

可以用 impl Trait 作为返回类型,隐藏具体类型,适合闭包和迭代器这种类型名很复杂的情况。但它只能返回单一类型,不能运行时动态选择。要运行时多态需要用 trait object:Box<dyn Trait> 或 &dyn Trait,用动态分派在运行时确定调用哪个方法。
rust
fn produce_summarizable() -> impl Summary {
    Article {
        title: "Rust".into(),
        content: "确实好".into(),
    }
}

// 返回不同类型?不行!
// fn returns_result(flag: bool) -> impl Summary {
//     if flag { Article {..} } else { Tweet {..} }
// }

// 运行时多态用 trait object
fn returns_dyn(flag: bool) -> Box<dyn Summary> {
    if flag {
        Box::new(Article { title: "A".into(), content: "...".into() })
    } else {
        Box::new(Tweet { username: "user".into(), content: "...".into() })
    }
}

// 使用 trait object
let summaries: Vec<Box<dyn Summary>> = vec![
    Box::new(Article { title: "...".into(), content: "...".into() }),
    Box::new(Tweet { username: "...".into(), content: "...".into() }),
];
impl Trait 返回和 dyn Trait 是不同的:前者编译时单态化,后者运行时动态分派有额外开销。

04. 常用的标准库 Trait

标准库有很多重要的 trait。Display 控制 {} 格式化(给用户看),Debug 控制 {:?}(给开发者看)。Clone 深拷贝,Copy 浅拷贝(栈上复制)。PartialEq 实现 == 比较,PartialOrd 实现排序。Drop 实现析构函数,在值离开作用域时调用,做资源清理。
rust
#[derive(Debug, Clone, PartialEq, PartialOrd)]
struct Point {
    x: f64,
    y: f64,
}

// 手动实现 Display
impl std::fmt::Display for Point {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

// 手动实现 Drop
struct Database {
    conn: Connection,
}

impl Drop for Database {
    fn drop(&mut self) {
        println!("关闭数据库连接");
        // self.conn.close();
    }
}

let p = Point { x: 3.0, y: 4.0 };
println!("{}", p);       // Display: (3, 4)
println!("{:?}", p);     // Debug: Point { x: 3.0, y: 4.0 }
Copy 和 Clone 的区别:Copy 是栈上按位复制(简单的值类型),Clone 可以包含堆内存的深拷贝。

05. 关联类型和 supertrait

Trait 里可以定义关联类型(associated type),用 type 关键字。实现时指定具体类型。关联类型比泛型参数更简洁,因为不需要在每处使用都写类型参数。一个 trait 可以要求另一个 trait 作为前提,这叫 supertrait:trait X: Y {},实现 X 必须先实现 Y。
rust
// 关联类型
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

struct Counter {
    count: u32,
}

impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
        Some(self.count)
    }
}

// supertrait
use std::fmt::Display;

trait Printable: Display {
    fn print(&self) {
        println!("{}", self);
    }
}

// 实现 Printable 必须先实现 Display
impl Printable for Point {}

// 使用
let p = Point { x: 1.0, y: 2.0 };
p.print();
什么时候用关联类型什么时候用泛型参数?一个类型只应有一次实现就用关联类型(如 Iterator),多种实现用泛型参数。

知识测验

1/4正确 0

Rust 中 trait 相当于其他语言的什么?