Rust trait
trait(特征)定义了一组类型可以实现的行为,有点像其他语言里的接口(interface)。Rust 就是用它来表达“这个类型能做某件事”的,你在泛型那一页见到的 trait 约束,背后靠的也是它。
定义 trait
Section titled “定义 trait”trait Speak { fn speak(&self) -> String;}这段代码的意思是:任何实现了 Speak 的类型,都必须提供一个返回 String 的 speak 方法。trait 本身只是一个约定,在有类型实现它之前,它什么也不做。
实现 trait
Section titled “实现 trait”trait Speak { fn speak(&self) -> String;}
struct Dog;struct Cat;
impl Speak for Dog { fn speak(&self) -> String { String::from("汪汪!") }}
impl Speak for Cat { fn speak(&self) -> String { String::from("喵喵!") }}
fn main() { let dog = Dog; let cat = Cat;
println!("{}", dog.speak()); println!("{}", cat.speak());}输出:
汪汪!喵喵!Dog 和 Cat 是两个毫不相干的类型,但它们都实现了 Speak,所以都能调用 .speak()。这和结构体那一页的 impl 块是同一个套路:impl Speak for Dog 只是在说“Dog 是这样满足 Speak 这个 trait 的”。
把 trait 用作函数参数
Section titled “把 trait 用作函数参数”真正的好处在这里:你可以写一个函数,接受任何实现了某个 trait 的类型,而不用关心它具体是哪个类型:
trait Speak { fn speak(&self) -> String;}
struct Dog;impl Speak for Dog { fn speak(&self) -> String { String::from("汪汪!") }}
fn announce(animal: &impl Speak) { println!("这只动物说:{}", animal.speak());}
fn main() { let dog = Dog; announce(&dog);}animal: &impl Speak 的意思是:“任何实现了 Speak 的类型都行,我不需要知道具体是哪一个。”再加一个同样实现了 Speak 的 Bird 类型,announce 不用改一行代码就能直接用它。
你已经用过的 trait
Section titled “你已经用过的 trait”还记得结构体那一页的 #[derive(Debug)] 吗?Debug 本身就是一个 trait,derive 只是替你自动生成了它的实现,省得你手写。你还会经常碰到这些:Clone(用于 .clone())、PartialEq(用于 ==)和 PartialOrd(用于 <、>,你在泛型那一页已经见过它被当作 trait 约束)。
trait 可以为方法提供一个默认的方法体,实现它的类型可以直接沿用,也可以覆盖(override)它:
trait Greet { fn name(&self) -> String;
fn greet(&self) -> String { format!("你好,{}!", self.name()) }}任何实现 Greet 的类型都只需要定义 name(),greet() 是白送的,除非它也想自己重写一个。
trait 的内容远不止这一页讲的这些。常用 trait那一页会带你过一遍每天都会用到的标准 trait,高级 trait则讲关联类型、supertrait(父 trait)等在大型代码库里常见的工具。
1. 会算面积的图形
Section titled “1. 会算面积的图形”定义一个 trait Area,只有一个方法 fn area(&self) -> f64。为 Rectangle(有 width 和 height)和 Square(有 side)实现它。打印一个 3 × 4 的矩形和一个边长为 5 的正方形的面积。提示:每个 impl Area for ... 块只需要这一个方法,方法体就是一次乘法。
查看答案
trait Area { fn area(&self) -> f64;}
struct Rectangle { width: f64, height: f64,}
struct Square { side: f64,}
impl Area for Rectangle { fn area(&self) -> f64 { self.width * self.height }}
impl Area for Square { fn area(&self) -> f64 { self.side * self.side }}
fn main() { let rect = Rectangle { width: 3.0, height: 4.0 }; let square = Square { side: 5.0 };
println!("矩形:{}", rect.area()); println!("正方形:{}", square.area());}输出:
矩形:12正方形:252. 一个函数,多种类型
Section titled “2. 一个函数,多种类型”定义一个 trait Describe,带有 fn describe(&self) -> String。为 Book(有 title)和 Movie(有 title 和 minutes)实现它。然后写一个函数 fn show(item: &impl Describe) 打印描述,并分别用这两种类型调用它。提示:用 format! 拼出每个描述字符串。
查看答案
trait Describe { fn describe(&self) -> String;}
struct Book { title: String,}
struct Movie { title: String, minutes: u32,}
impl Describe for Book { fn describe(&self) -> String { format!("图书:《{}》", self.title) }}
impl Describe for Movie { fn describe(&self) -> String { format!("电影:《{}》({} 分钟)", self.title, self.minutes) }}
fn show(item: &impl Describe) { println!("{}", item.describe());}
fn main() { let book = Book { title: String::from("霍比特人") }; let movie = Movie { title: String::from("飞屋环游记"), minutes: 96 };
show(&book); show(&movie);}输出:
图书:《霍比特人》电影:《飞屋环游记》(96 分钟)3. 沿用或覆盖默认实现
Section titled “3. 沿用或覆盖默认实现”拿这一页的 Greet trait 来用,它带有默认的 greet() 方法。为 Person 实现它,沿用默认实现;再为 Pirate 实现它,覆盖 greet(),改成海盗腔的 啊嗬,...!。提示:两个类型都必须定义 name(),只有 Pirate 还要自己写一个 greet()。
查看答案
trait Greet { fn name(&self) -> String;
fn greet(&self) -> String { format!("你好,{}!", self.name()) }}
struct Person { name: String,}
struct Pirate { name: String,}
impl Greet for Person { fn name(&self) -> String { self.name.clone() }}
impl Greet for Pirate { fn name(&self) -> String { self.name.clone() }
fn greet(&self) -> String { format!("啊嗬,{}!", self.name()) }}
fn main() { let ana = Person { name: String::from("Ana") }; let jack = Pirate { name: String::from("Jack") };
println!("{}", ana.greet()); println!("{}", jack.greet());}输出:
你好,Ana!啊嗬,Jack!- trait 定义了一个类型可以实现的行为,类似于接口。
impl TraitName for Type提供具体的实现。- 把
&impl TraitName用作参数类型,就能接受任何实现了这个 trait 的类型。 - trait 可以提供默认的方法体,实现者可以沿用,也可以覆盖。
Debug、Clone和PartialEq都是标准库里常见的 trait,#[derive(...)]可以自动实现它们。
小测验
1. trait 定义的是什么?
2. 参数类型写成 animal: &impl Speak 是什么意思?
3. #[derive(Debug)] 实际上做了什么?
4. trait 中的默认实现是什么?
5. 根据这一页,下面哪个是 trait,而不是结构体或枚举?
得分: 0 / 5
- Trait:定义共同行为 — Rust 程序设计语言:官方教程中讲定义 trait、默认方法、
impl Trait参数和 trait 约束的章节。 - 特质 trait — 通过例子学 Rust:一个包含 trait、默认方法和具体实现的可运行示例。
- 派生 — 通过例子学 Rust:
#[derive(...)]能帮你实现哪些标准 trait,附示例。 - 高级 trait — Rust 程序设计语言:下一步,讲关联类型、supertrait 和 newtype 模式。
- Traits — The Rust Reference(英文):trait 定义的精确规则,想了解全部细节时再看。