Menu
CoddyTech

动态分发

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

当你使用带有 trait 限定的泛型,例如 <T: Summary> 时,Rust 会在编译时确定确切的类型。这称为静态分发:编译器会为你使用的每种具体类型生成专门的代码。这样运行速度很快,但也有一个限制:一个变量只能保存一种特定类型。

如果你需要一个在运行时可以保存不同类型的变量,该怎么办?这时就要用到特征对象。使用 Box<dyn Trait>,你可以存储任何实现了该特征的类型:

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

struct Dog;
struct Cat;

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

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

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

dyn 关键字表示动态分派:Rust 会在运行时查找要调用哪个方法,而不是在编译时查找。trait 对象的大小未知,因此需要使用 Box;Box 提供了一个大小固定的指针。

可以这样理解:泛型表示“我适用于类型 T”,而特征对象表示“我适用于任何能做到这件事的类型”。代价是少量运行时开销,换来通过同一个变量处理不同类型的灵活性。

challenge icon

挑战

简单

让我们构建一个车辆租赁系统,来展示动态分发的强大之处!你将创建一个 trait,用来定义任何可租赁车辆能做什么,然后使用 Box<dyn Trait> 在同一个变量中存储不同类型的车辆。

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

  • vehicle.rs:定义一个公开的 Rentable trait,其中包含一个 rental_info 方法,该方法接受 &self 并返回一个 String。然后创建两个公开的结构体:
    • Car:包含一个公开的 model 字段(String)。它的 rental_info 应返回 Car: {model}
    • Bike:包含一个公开的 brand 字段(String)。它的 rental_info 应返回 Bike: {brand}
  • main.rs:引入你的车辆模块,并使用输入来演示动态分发。创建一个 Box<dyn Rentable> 变量,让它先存放一个 Car,打印其租赁信息,然后将同一个变量重新赋值为存放一个 Bike,并再次打印其租赁信息。

这里的关键在于,一个 Box<dyn Rentable> 类型的变量可以存放 Car 或 Bike:Rust 会在运行时确定要调用哪个 rental_info 方法。这就是动态分发的实际应用!

你的输出应显示两辆车的信息:

Car: {model}
Bike: {brand}

例如,输入 Tesla Model 3 和 Trek 时:

Car: Tesla Model 3
Bike: Trek

你会收到两个输入:车辆型号和自行车品牌。

亲手试试

mod vehicle;

use vehicle::{Rentable, Car, Bike};

fn main() {
    // 读取输入
    let mut car_model = String::new();
    std::io::stdin().read_line(&mut car_model).expect("Failed to read line");
    let car_model = car_model.trim().to_string();
    
    let mut bike_brand = String::new();
    std::io::stdin().read_line(&mut bike_brand).expect("Failed to read line");
    let bike_brand = bike_brand.trim().to_string();
    
    // TODO: 创建一个存放 Car 的 Box<dyn Rentable> 变量
    // 使用 rental_info() 方法输出它的租赁信息
    
    // TODO: 给同一个变量重新赋值,使其存放 Bike
    // 再次输出它的租赁信息
    
}
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Rust 在线编译器