前言
本文主要包含动态分派和静态分派的主要知识点。
静态分派
静态分派就是指程序具体调用哪个函数在编译阶段就能确定下来。Rust 中可以通过泛型和 impl trait
来完成静态分派。泛型我们之前已经做个介绍,这里就不赘述了。主要讲讲 impl trait
。
在我们使用泛型的时候,如果想返回复杂的迭代器类型或者匿名的闭包会很难受。这时我们就可以通过 impl trait
解决:
1
2
3
|
fn returns_closure() -> impl Fn(i32) -> i32 {
|x| x + 1
}
|
该语法目前还能使用在参数列表中:
1
2
3
|
fn in_fn(f: impl Fn(i32) -> i32) {
f(1);
}
|
虽然官方说还会增加更多的使用场景,不过个人发现有用的就是这么几种情况。因为很难有自定义类型只靠单独一两种 trait 描述,所以实际使用时很容易写出 impl XXX + Clone + XXX + XXX ...
一连套的东西,可读性依然很低。所以目前看它主要还是为了解决特定问题而存在。
trait object 与动态分派
trait object 指的是指向 trait 的指针。例如 MyTrait
的 trait object 可以是:&dyn MyTrait,&mut dyn MyTrait,Box<dyn MyTrait>,*const dyn MyTrait,*mut dyn MyTrait, Rc<dyn MyTrait>
等等。
当我们把 trait 当作类型的时候我们无法在编译期知道它的大小,属于动态类型(DST)。我们可以把 trait object 理解为:
1
2
3
4
|
struct TraitObject {
data: *mut (),
vtable: *mut ()
}
|
Rust 里面类型本身不包含虚函数表的指针,而是位于各个 trait object 里面。这也就意味着相同的类型的不同 trait object 的虚函数表也不一样。
借用这个特性我们可以完成一个经典应用场景:
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
|
trait Tickable {
fn tick(&self, delta_time: f32);
}
struct A {}
struct B {}
impl Tickable for A {
fn tick(&self, delta_time: f32) {
println!("A tick!")
}
}
impl Tickable for B {
fn tick(&self, delta_time: f32) {
println!("B tick!")
}
}
fn main() {
let mut components: Vec<Box<dyn Tickable>> = Vec::new();
components.push(Box::new(A {}));
components.push(Box::new(B {}));
components.iter().for_each(|c| c.tick(1.0));
}
|
object safety
为了保证动态分派不会出问题,我们需要对能够动态分派的 trait 做一些限制。例如如果我们动态分派 Clone trait 就会出问题,我们不知道克隆出来的到底是什么类型。例如:
1
2
3
4
5
6
7
|
trait SomeTrait {
fn foo(&self) -> int { ... }
// Object-safe methods may not return `Self`:
fn new() -> Self;
}
|
new
和 Clone 有同样的问题。为此 Rust 目前提供两种解决方案,一种是拆分 trait:
1
2
3
4
5
6
7
8
|
trait SomeTrait {
fn foo(&self) -> int { ... }
}
trait SomeTraitCtor : SomeTrait {
fn new() -> Self;
}
|
另一种就是加 Self::Sized
:
1
2
3
4
5
6
|
trait SomeTrait {
fn foo(&self) -> int { ... }
fn new() -> Self
where Self : Sized; // this condition is new
}
|
所以我们也能理解源码里面 Clone trait 为啥要继承 Sized 约束了:
1
2
3
4
5
|
#[stable(feature = "rust1", since = "1.0.0")]
#[lang = "clone"]
pub trait Clone: Sized {
// ...
}
|