0Pricing
Learn Rust Coding · 课时

特征对象与 dyn

动态分派

特征对象与 dyn 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。

问题:混合类型

当每次调用都使用一种具体类型时,带 Trait 约束的泛型非常好用。但如果您想要一个包含不同类型、且这些类型都共享某个 Trait 的集合,该怎么办?泛型无法容纳混合类型。

Trait 对象可以解决这个问题。

什么是 Trait 对象?

Trait 对象是通过指针和dyn关键字引用的值,例如&dyn Trait或Box<dyn Trait>。

它让您可以在运行时以统一方式处理多个具体类型。

dyn Trait 的 Box

Box<dyn Trait>拥有堆上某个实现了该 Trait 的类型的值。您无需知道具体类型,就可以调用 Trait 方法。

trait Speak { fn say(&self) -> String; }
struct Dog;
impl Speak for Dog { fn say(&self) -> String { String::from("Woof") } }

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.say());
}

Trait 对象的 Vec

它最大的优势是:只要每种类型都实现了该 Trait,一个向量就可以容纳许多不同类型。

trait Speak { fn say(&self) -> String; }
struct Dog;
struct Cat;
impl Speak for Dog { fn say(&self) -> String { String::from("Woof") } }
impl Speak for Cat { fn say(&self) -> String { String::from("Meow") } }

fn main() {
    let zoo: Vec<Box<dyn Speak>> = vec![Box::new(Dog), Box::new(Cat)];
    for animal in &zoo {
        println!("{}", animal.say());
    }
}

动态分派

对于特征对象,要调用的方法会在运行时通过查找隐藏表(虚函数表)来决定。这称为动态分派。

代价是一次很小的间接访问;好处是运行时的灵活性。

返回特征对象的函数

当具体类型不固定时,函数可以返回 Box<dyn Trait>。这对于在运行时决定类型的工厂函数很有用。

trait Speak { fn say(&self) -> String; }
struct Dog;
struct Cat;
impl Speak for Dog { fn say(&self) -> String { String::from("Woof") } }
impl Speak for Cat { fn say(&self) -> String { String::from("Meow") } }

fn make(kind: &str) -> Box<dyn Speak> {
    if kind == "dog" { Box::new(Dog) } else { Box::new(Cat) }
}

fn main() {
    println!("{}", make("cat").say());
}

借用的特征对象

如果不需要所有权,也可以通过 &dyn Trait 传递借用的特征对象。此方式不会进行堆分配。

trait Speak { fn say(&self) -> String; }
struct Dog;
impl Speak for Dog { fn say(&self) -> String { String::from("Woof") } }

fn announce(s: &dyn Speak) {
    println!("heard: {}", s.say());
}

fn main() {
    let d = Dog;
    announce(&d);
}

对象安全

并非每个特征都能成为特征对象。特征必须是对象安全的:简单来说,其方法不能按值返回 Self,也不能使用泛型类型参数。

接受 &self 并使用具体类型的方法没有问题。

对象不安全的示例

包含返回 Self 的方法的特征不能用作 dyn,因为指针背后的 Self 大小未知。请让特征对象所使用的特征保持简单。

插件式示例

特征对象非常适合插件系统:将处理器列表存放在 dyn 后面,然后无需关心具体类型即可运行所有处理器。

trait Task { fn run(&self) -> i32; }
struct Add { a: i32, b: i32 }
struct Negate { x: i32 }
impl Task for Add { fn run(&self) -> i32 { self.a + self.b } }
impl Task for Negate { fn run(&self) -> i32 { -self.x } }

fn main() {
    let tasks: Vec<Box<dyn Task>> = vec![Box::new(Add { a: 2, b: 3 }), Box::new(Negate { x: 7 })];
    let total: i32 = tasks.iter().map(|t| t.run()).sum();
    println!("total {}", total);
}

何时使用 dyn

当您需要异构集合、运行时选择的类型,或想减少大量泛型实例化造成的代码膨胀时,请使用特征对象。

否则,优先使用泛型以获得最高速度。

快速检查

检验您对特征对象的掌握程度。

回顾

您学习了动态分派:

  • 特征对象在指针后使用 dyn(Box<dyn T>、&dyn T)
  • 它们支持异构集合和运行时选择的类型
  • 方法调用会经过虚函数表(动态分派)
  • 特征必须是对象安全的,才能这样使用

常见问题解答

「特征对象与 dyn」课时是免费的吗?

是的 — 「特征对象与 dyn」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。

「特征对象与 dyn」这节课中我会学到什么?

动态分派 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Learn Rust Coding 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「特征对象与 dyn」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Learn Rust Coding 课中编写并运行代码吗?

能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 定义特征
  2. 特征对象与 dyn
  3. 静态分派与动态分派
  4. 默认方法
← 返回 Learn Rust Coding