云计算百科
云计算领域专业知识百科平台

【Rust入门知识点学与练】第24课:Trait 基础

知识点:定义和实现 Trait

Trait 定义了一组行为(方法),类型可以实现这些行为:

// 定义 trait
trait Speak {
fn speak(&self) -> String; // 必须实现的方法
fn greet(&self) -> String { // 带默认实现的方法
format!("你好!")
}
}

// 为不同类型实现 trait
struct Dog {
name: String,
}

struct Cat {
name: String,
}

impl Speak for Dog {
fn speak(&self) -> String {
format!("{}说:汪汪!", self.name)
}
// greet 使用默认实现
}

impl Speak for Cat {
fn speak(&self) -> String {
format!("{}说:喵~", self.name)
}

// 覆盖默认实现
fn greet(&self) -> String {
format!("{}懒洋洋地看了你一眼", self.name)
}
}

fn main() {
let dog = Dog { name: String::from("旺财") };
let cat = Cat { name: String::from("咪咪") };

println!("{}", dog.speak()); // 旺财说:汪汪!
println!("{}", dog.greet()); // 你好!
println!("{}", cat.speak()); // 咪咪说:喵~
println!("{}", cat.greet()); // 咪咪懒洋洋地看了你一眼
}

知识点:派生宏(derive)

标准库提供了很多可以自动实现的 trait:

// #[derive(…)] 自动实现常见 trait
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Color {
r: u8,
g: u8,
b: u8,
}

// 常用可派生的 trait:
// Debug — {:?} 格式化
// Clone — .clone() 深拷贝
// Copy — 自动按位复制(不能和 Drop 共存)
// PartialEq — == 和 !=
// Eq — 完全等价关系(要求 PartialEq)
// PartialOrd — < > <= >=
// Ord — 全序关系
// Hash — 可作为 HashMap 的键
// Default — 默认值

fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1.clone();
println!("p1: {:?}", p1);
println!("相等? {}", p1 == p2);

let red = Color { r: 255, g: 0, b: 0 };
println!("颜色: {:?}", red);

// Default trait
let default_point = Point { x: 0.0, y: 0.0 };
// 也可以手动实现 Default
}

// 手动实现 Default
impl Default for Point {
fn default() -> Self {
Point { x: 0.0, y: 0.0 }
}
}

知识点:Trait 作为参数

trait Area {
fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}

impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}

// 方式1:impl Trait 语法(简洁)
fn print_area(shape: &impl Area) {
println!("面积: {:.2}", shape.area());
}

// 方式2:泛型 + trait bound(更灵活)
fn print_area2<T: Area>(shape: &T) {
println!("面积: {:.2}", shape.area());
}

// 方式3:where 子句(多个 bound 时更清晰)
fn compare_areas<T: Area + std::fmt::Debug>(a: &T, b: &T) {
let area_a = a.area();
let area_b = b.area();
if area_a > area_b {
println!("{:?} 更大", a);
} else {
println!("{:?} 更大", b);
}
}

// 返回 trait 对象(动态分发)
fn create_shape(kind: &str) -> Box<dyn Area> {
match kind {
"circle" => Box::new(Circle { radius: 5.0 }),
"rectangle" => Box::new(Rectangle { width: 3.0, height: 4.0 }),
_ => panic!("未知形状"),
}
}

fn main() {
let circle = Circle { radius: 3.0 };
let rect = Rectangle { width: 4.0, height: 5.0 };

print_area(&circle); // 面积: 28.27
print_area(&rect); // 面积: 20.00

// 动态分发
let shape = create_shape("circle");
println!("动态面积: {:.2}", shape.area());
}

知识点:多个 Trait Bound

use std::fmt;

trait Drawable {
fn draw(&self);
}

trait Resizable {
fn resize(&mut self, factor: f64);
}

#[derive(Debug)]
struct Box {
size: f64,
}

impl Drawable for Box {
fn draw(&self) {
println!("绘制大小为 {} 的盒子", self.size);
}
}

impl Resizable for Box {
fn resize(&mut self, factor: f64) {
self.size *= factor;
}
}

impl fmt::Display for Box {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Box(size={})", self.size)
}
}

// 要求同时实现多个 trait
fn process<T: Drawable + Resizable + fmt::Display>(item: &mut T) {
println!("处理前: {}", item);
item.draw();
item.resize(2.0);
println!("处理后: {}", item);
item.draw();
}

// where 子句写法(更清晰)
fn process2<T>(item: &mut T)
where
T: Drawable + Resizable + fmt::Display,
{
println!("处理前: {}", item);
item.resize(0.5);
item.draw();
}

fn main() {
let mut b = Box { size: 10.0 };
process(&mut b);
}

知识点:trait 对象与 dyn

use std::fmt;

trait Animal: fmt::Display {
fn name(&self) -> &str;
fn sound(&self) -> &str;
}

struct Cow;
struct Sheep;

impl fmt::Display for Cow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "牛")
}
}

impl fmt::Display for Sheep {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "羊")
}
}

impl Animal for Cow {
fn name(&self) -> &str { "牛" }
fn sound(&self) -> &str { "哞~" }
}

impl Animal for Sheep {
fn name(&self) -> &str { "羊" }
fn sound(&self) -> &str { "咩~" }
}

// 用 Box<dyn Trait> 存储不同类型的 trait 实现
fn make_noise(animal: &dyn Animal) {
println!("{}说:{}", animal.name(), animal.sound());
}

fn main() {
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Cow),
Box::new(Sheep),
Box::new(Cow),
];

// 动态分发:遍历不同类型的动物
for animal in &animals {
make_noise(animal.as_ref());
}

// 也可以用 &dyn Trait
let cow = Cow;
make_noise(&cow);
}

知识点:为泛型类型实现 Trait

use std::fmt;

// 泛型结构体
#[derive(Debug)]
struct Wrapper<T> {
value: T,
}

// 为泛型类型实现 trait
impl<T: fmt::Display> fmt::Display for Wrapper<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}]", self.value)
}
}

// 为泛型类型实现自定义 trait
trait Unwrap {
type Inner;
fn unwrap_value(self) -> Self::Inner;
}

impl<T> Unwrap for Wrapper<T> {
type Inner = T;
fn unwrap_value(self) -> T {
self.value
}
}

fn main() {
let w = Wrapper { value: 42 };
println!("{}", w); // [42]

let w2 = Wrapper { value: String::from("hello") };
println!("{}", w2); // [hello]

let val = w2.unwrap_value();
println!("解包: {}", val);
}

核心规则

概念 写法
定义 trait trait Name { fn method(&self); }
实现 trait impl Name for Type { … }
默认方法 fn method(&self) -> T { … }
派生宏 #[derive(Debug, Clone, PartialEq)]
impl Trait 参数 fn foo(x: &impl Trait)
泛型 + bound fn foo<T: Trait>(x: &T)
where 子句 fn foo(x: &T) where T: Trait
多个 bound T: Trait1 + Trait2
trait 对象 Box / &dyn Trait
返回 trait 对象 -> Box

动手试试

补全下面的代码:

use std::fmt;

// 补全:定义一个 trait Shape,包含以下方法:
// 1. area(&self) -> f64 计算面积
// 2. perimeter(&self) -> f64 计算周长
// 3. describe(&self) -> String 返回描述(有默认实现)
// 默认实现返回格式: "面积={:.2}, 周长={:.2}"

// 补全:定义结构体 Triangle,字段为 a, b, c(三边长度,f64)

// 补全:为 Triangle 实现 Shape trait
// 面积用海伦公式:s = (a+b+c)/2, area = sqrt(s*(s-a)*(s-b)*(s-c))
// 周长 = a + b + c

// 补全:为 Triangle 实现 Display trait
// 格式: "三角形({},{},{})", a, b, c

// 补全:定义结构体 RegularPolygon(正多边形)
// 字段:sides: usize(边数), side_length: f64(边长)

// 补全:为 RegularPolygon 实现 Shape trait
// 面积 = (n * s²) / (4 * tan(π/n))
// 周长 = n * s

// 补全:为 RegularPolygon 实现 Display trait
// 格式: "正{n}边形(边长={})", sides, side_length

// 补全:实现函数 largest_shape
// 接受一个 &[Box<dyn Shape>],返回面积最大的形状的索引
fn largest_shape(shapes: &[Box<dyn Shape>]) -> usize {
// 补全
todo!()
}

// 补全:实现函数 total_area
// 接受一个 &[Box<dyn Shape>],返回所有形状的面积之和
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
// 补全
todo!()
}

fn main() {
// 补全:创建形状列表
// 1. 三角形 (3, 4, 5)
// 2. 正方形(正4边形,边长 5)
// 3. 正六边形(边长 3)
// 4. 三角形 (5, 12, 13)

// 补全:遍历打印每个形状的描述
// 期望输出(每行一个):
// 三角形(3,4,5): 面积=6.00, 周长=12.00
// 正4边形(边长=5): 面积=25.00, 周长=20.00
// 正6边形(边长=3): 面积=23.38, 周长=18.00
// 三角形(5,12,13): 面积=30.00, 周长=30.00

// 补全:打印最大面积的索引
// 期望: 最大面积索引: 3

// 补全:打印总面积
// 期望: 总面积: 84.38
}

期望输出:
三角形(3,4,5): 面积=6.00, 周长=12.00
正4边形(边长=5): 面积=25.00, 周长=20.00
正6边形(边长=3): 面积=23.38, 周长=18.00
三角形(5,12,13): 面积=30.00, 周长=30.00
最大面积索引: 3
总面积: 84.38

写好了贴过来,没问题就进入第25课:格式化输出与常用标准库 👋

贴代码

use std::fmt;
use std::f64::consts::PI;

// Shape trait,describe 提供默认实现
trait Shape {
fn area(&self) -> f64;
fn perimeter(&self) -> f64;

fn describe(&self) -> String {
format!("面积={:.2}, 周长={:.2}", self.area(), self.perimeter())
}
}

// 三角形结构体
struct Triangle {
a: f64,
b: f64,
c: f64,
}

impl Shape for Triangle {
fn area(&self) -> f64 {
let s = (self.a + self.b + self.c) / 2.0;
(s * (s self.a) * (s self.b) * (s self.c)).sqrt()
}

fn perimeter(&self) -> f64 {
self.a + self.b + self.c
}
}

impl fmt::Display for Triangle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "三角形({},{},{})", self.a, self.b, self.c)
}
}

// 正多边形
struct RegularPolygon {
sides: usize,
side_length: f64,
}

impl Shape for RegularPolygon {
fn area(&self) -> f64 {
let n = self.sides as f64;
let s = self.side_length;
(n * s * s) / (4.0 * (PI / n).tan())
}

fn perimeter(&self) -> f64 {
(self.sides as f64) * self.side_length
}
}

impl fmt::Display for RegularPolygon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "正{}边形(边长={})", self.sides, self.side_length)
}
}

// 返回面积最大形状的索引
fn largest_shape(shapes: &[Box<dyn Shape>]) -> usize {
shapes
.iter()
.enumerate()
.max_by(|(_, s1), (_, s2)| s1.area().partial_cmp(&s2.area()).unwrap())
.map(|(idx, _)| idx)
.unwrap()
}

// 全部形状面积之和
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
shapes.iter().map(|s| s.area()).sum()
}

fn main() {
// 创建动态形状数组
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Triangle { a: 3.0, b: 4.0, c: 5.0 }),
Box::new(RegularPolygon { sides: 4, side_length: 5.0 }),
Box::new(RegularPolygon { sides: 6, side_length: 3.0 }),
Box::new(Triangle { a: 5.0, b: 12.0, c: 13.0 }),
];

// 遍历打印
for s in &shapes {
// 需要同时拿到Display和Shape,这里分开处理;为了方便我们重新包装
// 为了满足打印 "三角形(3,4,5): 面积=6.00, 周长=12.00",
// dyn Shape 本身没有Display,所以我们改用模式匹配打印(练习版本)
// 下面主循环完整实现
}

// 方案:重建带打印的版本,完整main实现:
let tri1 = Triangle { a: 3.0, b: 4.0, c: 5.0 };
let sq = RegularPolygon { sides: 4, side_length: 5.0 };
let hex = RegularPolygon { sides: 6, side_length: 3.0 };
let tri2 = Triangle { a: 5.0, b: 12.0, c: 13.0 };

let shape_vec: Vec<Box<dyn Shape>> = vec![
Box::new(tri1),
Box::new(sq),
Box::new(hex),
Box::new(tri2),
];

// 注意:dyn Shape 不能直接Display;为了完成题目输出,下面把对象再存一份用于打印
// 完整版main(保存各个实体用于格式化输出)
let items = [
(&Triangle { a: 3.0, b: 4.0, c: 5.0 }) as &dyn fmt::Display,
&RegularPolygon { sides: 4, side_length: 5.0 } as &dyn fmt::Display,
&RegularPolygon { sides: 6, side_length: 3.0 } as &dyn fmt::Display,
&Triangle { a: 5.0, b: 12.0, c: 13.0 } as &dyn fmt::Display,
];

for i in 0..shape_vec.len() {
println!("{}: {}", items[i], shape_vec[i].describe());
}

let max_idx = largest_shape(&shape_vec);
println!("最大面积索引: {}", max_idx);

let sum = total_area(&shape_vec);
println!("总面积: {:.2}", sum);
}

运行输出

plaintext

三角形(3,4,5): 面积=6.00, 周长=12.00
正4边形(边长=5): 面积=25.00, 周长=20.00
正6边形(边长=3): 面积=23.38, 周长=18.00
三角形(5,12,13): 面积=30.00, 周长=30.00
最大面积索引: 3
总面积: 84.38

核心知识点

1. Trait 默认方法

fn describe(&self) -> String { }

没有 &self 前面没有 dyn ,直接写在trait里面,不用每个实现者重复实现。

2. 海伦公式(三角形面积)

s=(a+b+c)/2,\\quad area=\\sqrt{s(sa)(sb)(sc)}

3. 正多边形面积公式

A=\\dfrac{n\\cdot s^{2}}{4\\cdot\\tan(\\pi/n)}

需要 std::f64::consts::PI 。

**4. Trait 对象(动态分发) Box **

  • Box :堆分配,运行时动态调用 area/perimeter;
  • 一个Vec里面存放不同类型(Triangle、RegularPolygon)的形状;
  • 限制:一个trait对象不能同时拥有多个不同trait(Shape+Display),所以main中分开两份引用,一份做Shape运算,一份做Display打印,这是Rust经典坑点。

5. largest_shape

enumerate 带上索引, max_by ,f64比较用 partial_cmp (因为存在NaN)。

6. total_area

迭代器map + sum,一行代码完成求和。

Rust 面向对象(Trait对象)训练到此结束

现在Rust全套主线练习题:基础 → 所有权 → 生命周期 → 泛型 → trait(关联类型)→ 闭包迭代器 → 容器 → 宏 → 自定义迭代器 → trait对象动态多态 → 错误处理 → 多线程(Arc‑Mutex‑channel)全部通关。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【Rust入门知识点学与练】第24课:Trait 基础
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!