Menu
CoddyTech

Trait 向量

本课属于 Coddy Rust 学习旅程的“面向对象编程”单元。第 46 课,共 61 课。

你已经看到,Box<dyn Trait> 可以让单个变量保存不同的类型。当你把它与集合结合起来时,它的真正威力就显现出来了:在一个向量中存储多种不同的类型。

普通向量要求所有元素的类型都相同。你不能创建一个同时包含猫的 Vec<Dog>。不过,借助 trait 对象,你可以创建一个向量,其中可以存放任何实现了共同 trait 的类型:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;
struct Bird;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

impl Speak for Bird {
    fn speak(&self) -> String { String::from("Tweet!") }
}

fn main() {
    let animals: Vec<Box<dyn Speak>> = vec![
        Box::new(Dog),
        Box::new(Cat),
        Box::new(Bird),
    ];
}

类型 Vec<Box<dyn Speak>> 表示“装有装箱特征对象的向量”。每个元素都是一个 Box,指向实现了 Speak 的对象:实际类型可以不同。将值添加到向量时,你需要用 Box::new() 包装每个值。

这种模式对于构建灵活的系统至关重要,尤其是在需要管理相关但不同的对象集合时,例如 UI 组件、游戏实体或插件系统。

challenge icon

挑战

简单

让我们构建一个通知系统,用来处理不同类型的提醒!你将创建一个集合,用单个向量存储各种通知类型,包括电子邮件、SMS 消息和推送通知,并使用 trait 对象来实现。

你将把代码分成两个文件:

  • notifications.rs:定义一个公开的 Notify trait,其中包含一个 send 方法。该方法接收 &self,并返回一个描述所发送通知的 String。然后创建三个公开的结构体:
    • Email:包含一个公开的 recipient 字段(String)。它的 send 应返回 Email to: {recipient}
    • Sms:包含一个公开的 phone 字段(String)。它的 send 应返回 SMS to: {phone}
    • Push:包含一个公开的 device 字段(String)。它的 send 应返回 Push to: {device}
  • main.rs:引入 notifications 模块,并创建一个类型为 Vec<Box<dyn Notify>> 的向量,用来存放这三种通知类型。使用提供的输入创建每种通知各一个,按顺序(Email、Sms、Push)将它们添加到向量中,然后打印对向量第一个元素调用 send() 的结果。

这里的关键概念是,尽管 Email、Sms 和 Push 是完全不同的结构体,但由于它们共享 Notify trait,因此可以一起存放在同一个向量中。每个元素都用 Box::new() 包装,以创建 trait 对象。

你的输出应显示第一条通知:

Email to: {recipient}

例如,输入 alice@example.com、555-1234 和 iPhone-12 时:

Email to: alice@example.com

你将收到三个输入:电子邮件收件人、电话号码和设备名称。

亲手试试

mod notifications;

use notifications::{Notify, Email, Sms, Push};

fn main() {
    // 读取输入
    let mut recipient = String::new();
    std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
    let recipient = recipient.trim().to_string();

    let mut phone = String::new();
    std::io::stdin().read_line(&mut phone).expect("Failed to read line");
    let phone = phone.trim().to_string();

    let mut device = String::new();
    std::io::stdin().read_line(&mut device).expect("Failed to read line");
    let device = device.trim().to_string();

    // TODO: 创建一个 Vec<Box<dyn Notify>>,用于存放不同类型的通知
    // TODO: 使用输入创建 Email、Sms 和 Push 实例
    // TODO: 使用 Box::new(),按顺序(Email、Sms、Push)将它们添加到向量中
    // TODO: 输出对第一个元素调用 send() 的结果
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Rust 在线编译器