第2章 - Trait
嗨,朋友!我是长安。
上一章我们学习了泛型,这一章要学习一个更强大的特性——Trait。
Trait 有点像其他语言中的"接口"(Interface),但功能更强大。它定义了一组方法,任何实现了这个 Trait 的类型都必须提供这些方法。
听起来有点抽象?别担心,我会用最简单的方式给你讲清楚。
🤔 什么是 Trait?
Trait 就是定义共同行为的方式。
想象一下,不同的动物都会"叫",但叫声不同:
- 狗会"汪汪"
- 猫会"喵喵"
- 鸭子会"嘎嘎"
虽然叫声不同,但它们都有"叫"这个行为。在 Rust 中,我们可以用 Trait 来表示这种共同行为。
长安说
Trait 就像是一份合同或协议,规定了"如果你想成为某种类型,你必须能做这些事情"。
📖 定义 Trait
使用 trait 关键字定义:
trait Animal {
fn make_sound(&self) -> String;
}
这个 Trait 定义了一个方法签名 make_sound,但没有实现。实现由具体的类型来完成。
🏗️ 实现 Trait
使用 impl Trait for Type 语法:
struct Dog {
name: String,
}
struct Cat {
name: String,
}
impl Animal for Dog {
fn make_sound(&self) -> String {
format!("{}说: 汪汪!", self.name)
}
}
impl Animal for Cat {
fn make_sound(&self) -> String {
format!("{}说: 喵喵!", self.name)
}
}
fn main() {
let dog = Dog { name: String::from("旺财") };
let cat = Cat { name: String::from("咪咪") };
println!("{}", dog.make_sound()); // 旺财说: 汪汪!
println!("{}", cat.make_sound()); // 咪咪说: 喵喵!
}
🎯 默认实现
Trait 可以提供默认实现:
trait Greet {
fn greet(&self) -> String {
String::from("你好!") // 默认实现
}
}
struct Person;
struct Robot;
impl Greet for Person {} // 使用默认实现
impl Greet for Robot {
fn greet(&self) -> String { // 自定义实现
String::from("beep boop, 你好!")
}
}
fn main() {
let person = Person;
let robot = Robot;
println!("{}", person.greet()); // 你好!
println!("{}", robot.greet()); // beep boop, 你好!
}
🔗 Trait 作为参数
这是 Trait 最强大的用途之一:你可以接受任何实现了某个 Trait 的类型。
fn make_animal_sound(animal: &impl Animal) {
println!("动物叫声: {}", animal.make_sound());
}
fn main() {
let dog = Dog { name: String::from("旺财") };
let cat = Cat { name: String::from("咪咪") };
make_animal_sound(&dog);
make_animal_sound(&cat);
}
&impl Animal 的意思是:"接受任何实现了 Animal Trait 的类型的引用"。
Trait Bound 语法
还有一种更正式的写法:
fn make_animal_sound<T: Animal>(animal: &T) {
println!("动物叫声: {}", animal.make_sound());
}
<T: Animal> 叫做 Trait Bound(Trait 约束),表示泛型 T 必须实现 Animal Trait。
长安说
两种写法效果一样:
impl Trait语法:简洁,适合简单情况- Trait Bound 语法:灵活,适合复杂情况
🎨 多个 Trait 约束
一个类型可以同时要求实现多个 Trait:
use std::fmt::Display;
fn print_and_sound<T: Animal + Display>(item: &T) {
println!("信息: {}", item); // 需要 Display
println!("声音: {}", item.make_sound()); // 需要 Animal
}
使用 + 号连接多个 Trait。
where 子句
当约束很多时,可以用 where 子句让代码更清晰:
fn complex_function<T, U>(t: &T, u: &U)
where
T: Animal + Display,
U: Clone + Debug,
{
// 函数体
}
🔄 Trait 作为返回值
你也可以返回实现了某个 Trait 的类型:
fn get_animal(kind: &str) -> impl Animal {
if kind == "dog" {
Dog { name: String::from("旺财") }
} else {
Dog { name: String::from("默认狗") }
}
}
注意
使用 impl Trait 作为返回值时,只能返回同一种具体类型。上面的例子都返回 Dog,所以没问题。
如果想返回不同类型,需要使用 Trait 对象(下面会讲)。
📦 常用的标准库 Trait
Rust 标准库提供了很多有用的 Trait:
1. Display 和 Debug
用于格式化输出:
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: 3, y: 4 };
println!("点: {}", p); // 使用 Display
}
2. Clone 和 Copy
用于复制值:
#[derive(Clone, Copy)] // 自动实现
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // Copy
let p3 = p1.clone(); // Clone
println!("p1: ({}, {})", p1.x, p1.y); // p1 依然有效
}
3. PartialEq 和 Eq
用于比较相等性:
#[derive(PartialEq)]
struct Person {
name: String,
age: u32,
}
fn main() {
let p1 = Person { name: String::from("长安"), age: 25 };
let p2 = Person { name: String::from("长安"), age: 25 };
if p1 == p2 {
println!("两个人相同");
}
}
4. PartialOrd 和 Ord
用于比较大小:
#[derive(PartialEq, PartialOrd)]
struct Height(f64);
fn main() {
let h1 = Height(1.75);
let h2 = Height(1.80);
if h1 < h2 {
println!("h1 更矮");
}
}
🎭 Derive 属性
对于常见的 Trait,Rust 可以自动生成实现:
#[derive(Debug, Clone, PartialEq)]
struct User {
name: String,
age: u32,
}
fn main() {
let user = User {
name: String::from("长安"),
age: 25,
};
println!("{:?}", user); // Debug
let user2 = user.clone(); // Clone
println!("相等吗? {}", user == user2); // PartialEq
}
常用的可 derive 的 Trait:
Debug- 调试输出Clone- 克隆Copy- 复制PartialEq- 相等比较Eq- 严格相等PartialOrd- 大小比较Ord- 严格排序Hash- 哈希
🎯 实战例子:可排序的学生列表
#[derive(Debug, Clone, PartialEq, Eq)]
struct Student {
name: String,
score: u32,
}
impl PartialOrd for Student {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Student {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// 按分数从高到低排序
other.score.cmp(&self.score)
}
}
fn main() {
let mut students = vec![
Student { name: String::from("小明"), score: 85 },
Student { name: String::from("小红"), score: 92 },
Student { name: String::from("小刚"), score: 78 },
];
students.sort(); // 可以排序了!
for student in students {
println!("{}: {} 分", student.name, student.score);
}
}
输出:
小红: 92 分
小明: 85 分
小刚: 78 分
🔍 Trait 对象
如果你想在运行时处理不同类型,可以使用 Trait 对象:
trait Animal {
fn make_sound(&self) -> String;
}
struct Dog;
struct Cat;
impl Animal for Dog {
fn make_sound(&self) -> String {
String::from("汪汪!")
}
}
impl Animal for Cat {
fn make_sound(&self) -> String {
String::from("喵喵!")
}
}
fn main() {
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Dog),
];
for animal in animals {
println!("{}", animal.make_sound());
}
}
dyn Animal 表示"实现了 Animal Trait 的某种类型"。
长安说
impl Trait:编译时确定类型(静态分发,快)dyn Trait:运行时确定类型(动态分发,灵活但稍慢)
💡 小结
在这一章,我们学会了:
- Trait 定义共同行为
- 使用
trait定义,impl Trait for Type实现 - 默认实现简化代码
- Trait 作为参数接受多种类型
- Trait Bound 约束泛型
- Derive 属性自动实现常用 Trait
- Trait 对象实现运行时多态
核心要点:
- Trait 像接口,定义类型必须实现的方法
- 可以有默认实现
impl Traitvs Trait Bound:&impl Tvs<T: Trait>derive可以自动实现常用 Traitdyn Trait用于动态分发
长安的建议
Trait 是 Rust 最强大的特性之一,配合泛型使用威力无穷!刚开始可能觉得复杂,但多写几次就会发现它让代码变得更灵活、更安全。
记住:Trait 就是"这个类型能做什么"的约定!
🚀 下一步
现在你已经掌握了泛型和 Trait,下一章我们要学习一个更高级的概念——生命周期。
生命周期帮助 Rust 确保引用总是有效的,这是 Rust 安全性的另一个重要支柱!
💪 练习题
- 定义一个
DescribableTrait,有一个describe()方法返回字符串,为Book和Movie实现它 - 写一个函数,接受任何实现了
Display和CloneTrait 的类型,打印并克隆它 - 为自定义类型实现
PartialEq,让两个实例可以用==比较 - 创建一个 Trait 对象的 Vec,存储不同类型但都实现了同一个 Trait
答案示例
use std::fmt::Display;
// 练习1
trait Describable {
fn describe(&self) -> String;
}
struct Book {
title: String,
author: String,
}
struct Movie {
title: String,
director: String,
}
impl Describable for Book {
fn describe(&self) -> String {
format!("《{}》 by {}", self.title, self.author)
}
}
impl Describable for Movie {
fn describe(&self) -> String {
format!("《{}》 directed by {}", self.title, self.director)
}
}
// 练习2
fn print_and_clone<T: Display + Clone>(item: &T) -> T {
println!("打印: {}", item);
item.clone()
}
// 练习3
#[derive(Clone)]
struct Product {
name: String,
price: f64,
}
impl PartialEq for Product {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.price == other.price
}
}
// 练习4
trait Shape {
fn area(&self) -> f64;
}
struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }
impl Shape for Circle {
fn area(&self) -> f64 {
3.14159 * self.radius * self.radius
}
}
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 5.0 }),
Box::new(Rectangle { width: 4.0, height: 6.0 }),
];
for shape in shapes {
println!("面积: {}", shape.area());
}
}
