Skip to content

Trait 特征

Trait 是 Rust 中定义共享行为的方式,类似于其他语言中的接口(interface)。通过 Trait,可以定义一组方法签名,然后让不同的类型实现这些方法。

pub trait Summary {
fn summarize(&self) -> String;
}

这定义了一个 Summary trait,任何实现它的类型都必须提供 summarize 方法。

pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}

实现 trait 有一个限制(孤儿规则):

  • trait 或类型至少有一个是在当前 crate 中定义的

这防止了两个 crate 对同一类型实现同一 trait 而产生冲突。

Trait 可以提供默认实现:

pub trait Summary {
fn summarize_author(&self) -> String;
// 默认实现,可以调用其他 trait 方法
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// 使用默认的 summarize 实现
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}

两种写法等价,但 trait bound 在复杂情况下更灵活:

// 两个参数必须是同一类型
pub fn notify<T: Summary>(item1: &T, item2: &T) { }
// 两个参数可以是不同类型
pub fn notify(item1: &impl Summary, item2: &impl Summary) { }

使用 + 指定多个 trait:

use std::fmt::Display;
pub fn notify(item: &(impl Summary + Display)) {
println!("{}", item);
}
// 或使用 trait bound 语法
pub fn notify<T: Summary + Display>(item: &T) {
println!("{}", item);
}

当 trait bound 很多时,使用 where 更清晰:

use std::fmt::{Debug, Display};
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// ...
0
}
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
}
}

限制:只能返回单一类型。以下代码不能编译:

fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle { /* ... */ }
} else {
Tweet { /* ... */ } // 错误!不能返回不同类型
}
}

使用 Trait Bound 有条件地实现方法

Section titled “使用 Trait Bound 有条件地实现方法”
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
// 只有当 T 实现了 Display 和 PartialOrd 时才有这个方法
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("最大的是 x = {}", self.x);
} else {
println!("最大的是 y = {}", self.y);
}
}
}

为所有满足条件的类型实现 trait:

// 标准库中的例子:为所有实现了 Display 的类型实现 ToString
impl<T: Display> ToString for T {
fn to_string(&self) -> String {
// ...
}
}

这就是为什么可以对任何实现了 Display 的类型调用 to_string()

格式化调试输出:

#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 1, y: 2 };
println!("{:?}", p); // Point { x: 1, y: 2 }
println!("{:#?}", p); // 美化输出
}
#[derive(Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone(); // 显式克隆
let p3 = p1; // Copy,p1 仍然可用
}

Copy 要求所有字段都是 Copy 的,且不能实现 Drop

提供默认值:

#[derive(Default)]
struct Config {
debug: bool,
timeout: u32,
}
fn main() {
let config = Config::default();
// 或
let config: Config = Default::default();
}

手动实现:

impl Default for Config {
fn default() -> Self {
Config {
debug: false,
timeout: 30,
}
}
}

相等比较:

#[derive(PartialEq, Eq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 2 };
println!("{}", p1 == p2); // true
}
  • PartialEq:允许部分相等(如浮点数 NaN != NaN)
  • Eq:完全相等(要求自反性)

排序比较:

#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let mut points = vec![
Point { x: 2, y: 3 },
Point { x: 1, y: 2 },
Point { x: 3, y: 1 },
];
points.sort();
}

用户友好的输出:

use std::fmt;
struct Point {
x: i32,
y: i32,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let p = Point { x: 1, y: 2 };
println!("{}", p); // (1, 2)
}

类型转换:

struct Wrapper(i32);
impl From<i32> for Wrapper {
fn from(value: i32) -> Self {
Wrapper(value)
}
}
fn main() {
let w1 = Wrapper::from(42);
let w2: Wrapper = 42.into(); // Into 自动实现
}

使用 dyn Trait 实现动态分发:

pub trait Draw {
fn draw(&self);
}
pub struct Button {
pub label: String,
}
impl Draw for Button {
fn draw(&self) {
println!("绘制按钮: {}", self.label);
}
}
pub struct TextField {
pub placeholder: String,
}
impl Draw for TextField {
fn draw(&self) {
println!("绘制文本框: {}", self.placeholder);
}
}
// 使用 trait 对象存储不同类型
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
fn main() {
let screen = Screen {
components: vec![
Box::new(Button { label: String::from("OK") }),
Box::new(TextField { placeholder: String::from("输入...") }),
],
};
screen.run();
}
  • 静态分发(泛型 + trait bound):编译时确定类型,性能更好
  • 动态分发dyn Trait):运行时确定类型,更灵活

定义 Summary trait,为 BookMovie 实现它:

trait Summary {
fn summarize(&self) -> String;
}
struct Book {
title: String,
author: String,
}
struct Movie {
title: String,
director: String,
}
// 实现 Summary
fn main() {
let book = Book {
title: String::from("Rust 编程"),
author: String::from("作者"),
};
let movie = Movie {
title: String::from("Rust: The Movie"),
director: String::from("导演"),
};
println!("{}", book.summarize());
println!("{}", movie.summarize());
}

编写函数接收任意实现了 Summary 的类型:

fn print_summary(item: &impl Summary) {
// 你的代码
}
// 使用 trait bound 语法重写
fn print_summary_v2<T: Summary>(item: &T) {
// 你的代码
}

Config 结构体实现 Default trait:

struct Config {
host: String,
port: u16,
debug: bool,
}
impl Default for Config {
// 你的代码
}
fn main() {
let config = Config::default();
println!("{}:{}, debug={}", config.host, config.port, config.debug);
}

Point 实现 Display trait:

use std::fmt;
struct Point {
x: f64,
y: f64,
}
impl fmt::Display for Point {
// 你的代码,格式: (x, y)
}
fn main() {
let p = Point { x: 3.14, y: 2.71 };
println!("{}", p); // 应输出: (3.14, 2.71)
}

学完 Trait 后,下一章我们将学习泛型编程,它与 Trait 结合使用可以编写高度抽象的代码。