Please enable Javascript to view the contents

[Rust 基础知识]动态分派和静态分派

 ·  ☕ 2 分钟

前言

    本文主要包含动态分派和静态分派的主要知识点。

静态分派

    静态分派就是指程序具体调用哪个函数在编译阶段就能确定下来。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 {
    // ...
}
分享

saberuster
作者
saberuster
一个喜欢编程和折腾的追风少年