Trait 特征
Trait 是 Rust 中定义共享行为的方式,类似于其他语言中的接口(interface)。通过 Trait,可以定义一组方法签名,然后让不同的类型实现这些方法。
定义 Trait
Section titled “定义 Trait”pub trait Summary { fn summarize(&self) -> String;}这定义了一个 Summary trait,任何实现它的类型都必须提供 summarize 方法。
为类型实现 Trait
Section titled “为类型实现 Trait”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 实现}Trait 作为参数
Section titled “Trait 作为参数”impl Trait 语法
Section titled “impl Trait 语法”pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize());}Trait Bound 语法
Section titled “Trait Bound 语法”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 Bound
Section titled “多个 Trait Bound”使用 + 指定多个 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);}where 子句
Section titled “where 子句”当 trait bound 很多时,使用 where 更清晰:
use std::fmt::{Debug, Display};
fn some_function<T, U>(t: &T, u: &U) -> i32where T: Display + Clone, U: Clone + Debug,{ // ... 0}Trait 作为返回类型
Section titled “Trait 作为返回类型”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); } }}Blanket Implementation
Section titled “Blanket Implementation”为所有满足条件的类型实现 trait:
// 标准库中的例子:为所有实现了 Display 的类型实现 ToStringimpl<T: Display> ToString for T { fn to_string(&self) -> String { // ... }}这就是为什么可以对任何实现了 Display 的类型调用 to_string()。
常用标准库 Trait
Section titled “常用标准库 Trait”格式化调试输出:
#[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); // 美化输出}Clone 和 Copy
Section titled “Clone 和 Copy”#[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。
Default
Section titled “Default”提供默认值:
#[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, } }}PartialEq 和 Eq
Section titled “PartialEq 和 Eq”相等比较:
#[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:完全相等(要求自反性)
PartialOrd 和 Ord
Section titled “PartialOrd 和 Ord”排序比较:
#[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();}Display
Section titled “Display”用户友好的输出:
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)}From 和 Into
Section titled “From 和 Into”类型转换:
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 自动实现}Trait 对象
Section titled “Trait 对象”使用 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();}静态分发 vs 动态分发
Section titled “静态分发 vs 动态分发”- 静态分发(泛型 + trait bound):编译时确定类型,性能更好
- 动态分发(
dyn Trait):运行时确定类型,更灵活
练习 1:定义并实现 Trait
Section titled “练习 1:定义并实现 Trait”定义 Summary trait,为 Book 和 Movie 实现它:
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());}练习 2:使用 Trait Bound
Section titled “练习 2:使用 Trait Bound”编写函数接收任意实现了 Summary 的类型:
fn print_summary(item: &impl Summary) { // 你的代码}
// 使用 trait bound 语法重写fn print_summary_v2<T: Summary>(item: &T) { // 你的代码}练习 3:实现 Default
Section titled “练习 3:实现 Default”为 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);}练习 4:实现 Display
Section titled “练习 4:实现 Display”为 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)}- The Rust Book - Trait
- Rust By Example - Traits
- Rust Reference - Traits
- std::fmt::Display
- std::default::Default
学完 Trait 后,下一章我们将学习泛型编程,它与 Trait 结合使用可以编写高度抽象的代码。