Rust dyn 关键字

是什么

在 Rust 中,dyn 关键字用于显式标记一个 trait 对象。Trait 对象是一种允许对不同类型的值进行统一处理的动态分发机制,它们通过某个共同的 trait 来进行抽象。

在 Rust 2015 版或更早的版本中,dyn 关键字不是必需的。但是,从 Rust 2018 版开始,为了提高代码的可读性和一致性,Rust 推荐使用 dyn 关键字来明确地表示 trait 对象。

作用

  1. 类型安全dyn 明确表示编译器在这里使用动态分发。因此,使用 dyn Trait 时,Rust 会在运行时而不是编译时解析方法调用,这允许我们在单个变量中存储实现了同一 trait 的不同类型的值。

  2. 清晰性:在代码中显式区分动态分发和静态分发(例如,通过泛型)有助于理解代码的行为,尤其是在复杂的项目中。

  3. 向前兼容:明确区分动态和静态分发可以帮助未来的 Rust 版本在不破坏现有代码的情况下引入新的功能或变化。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
trait Speak {
fn say(&self);
}

struct Dog;
struct Cat;

impl Speak for Dog {
fn say(&self) {
println!("Woof!");
}
}

impl Speak for Cat {
fn say(&self) {
println!("Meow!");
}
}

fn make_noise(animal: &dyn Speak) {
animal.say();
}

fn main() {
let dog = Dog;
let cat = Cat;

make_noise(&dog);
make_noise(&cat);
}

不使用

在上面的例子中,如果你省略 dyn 关键字,你不会得到一个编译错误,因为从 Rust 2018 edition 开始,dyn 关键字是可选的,但是强烈建议使用它来编写更清晰的代码。使用 dyn 关键字可以明确地表明函数参数是一个动态分发的特征对象。

如果你省略了 dyn 关键字,代码仍然会工作,因为在 Rust 2015 edition 中,这是默认的行为,而在 Rust 2018 edition 及以后,它是允许但不推荐的做法。不过,你的代码可能会不太清晰,因为它不明确地表明特征对象是动态分发的。

下面是省略 dyn 关键字的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
trait Speak {
fn say(&self);
}

// ...(其他代码和之前一样)

fn make_noise(animal: &Speak) {
animal.say();
}

// ...(其他代码和之前一样)

fn make_noise<T: Speak>(animal: &T) {
animal.say();
}

在这种情况下,Rust 编译器会为传递给 make_noise 函数的每种类型生成不同的代码实例。这种方法在编译时需要知道所有可能的类型,并且不会有运行时的性能开销,因为不需要通过虚拟方法表来动态查找方法。

性能差别

  • 静态分发:155.601083ms
  • 动态分发:153.239916ms