理解动态与静态调度实现

  在这一节里,介绍Rust语言调度动态与静态函数的方式。动态与静态调度函数是计算机语言广泛应用的概念和技术。

学习内容

  • 了解和学习Rust语言静态和动态函数概念
  • 理解和掌握静态和动态函数基本实现

篇目

最常见静态函数实例

  Ⓓ 在默认情况下,Rust语言方法都是静态函数。如下面代码的函数static_dispatch()

  静态调度或者分派(static dispatch)在编译时就知道被调用方是谁,而动态调度只有在运行时,才知道被调用方是谁。显然,在常见的情况下,相比动态调度,在运行程序时,静态调度会更快速,而相比静态调度,在编译程序时,动态调度会更快速。

  在使用静态分派会更有效,因为总是可以使用静态分派包装器函数来执行动态调度,但反之则不然。由于这个原因,比如标准库尝试在尽可能的情况下进行静态调度。

// File: examples/simple_static_dispatch.rs
#[derive(Default)]
struct NormalStruct {
    data: (u8)
}

impl NormalStruct {
    fn static_dispatch(&self) -> (u8) { (self.data) }
}

// clear && cargo run --example simple_static_dispatch
fn main() {
    let instance_struct :NormalStruct = Default::default();
    assert_eq!((0u8), instance_struct.static_dispatch());
}

静态函数的动态调度实例

  上面程序与下面程序的前面一部分是完全一致的。而下面程序的后面一部分代码动态方式调度静态函数。

  下面程序的关键词dyn就是告诉编译器需要进行动态方式调度。但是,要是关键词dyn不在,也能够通过编译,只是有警告提示“不带显式dyn的衔接特质对象已弃用”,这告诉我们代码需要增加动态调度关键词dyn

  这个警告提示提供了一条信息:我们正在涉及到的是衔接特质的一个或者一组对象,之所以需要动态方式,是因为,在Rust语言里衔接特质关键词trait可以针对任何类型实现功能,这是一种未知类型行为的随时调度方式。衔接特质的对象如同一个衔接插口,可以随时插到任何一个类型上。

  在动态调度时,Rust语言需要衔接特质对象的指针。从下面后面的三段代码里,可以理解到这个概念。比如。指针类型Box把实例instance_struct包装为指针类型;类型Vec的内部项都是实例instance_struct的指针类型。

// File: examples/simple_dynamic_dispatch.rs
trait Trait {
    fn static_dispatch(&self) -> (u8);
}

#[derive(Default)]
struct Struct {data: (u8)}

impl Trait for Struct {
    fn static_dispatch(&self) -> (u8) {
        (self.data)
    }
}

// clear && cargo run --example simple_dynamic_dispatch
fn main () {
    let instance_struct: Struct = Default::default();
    assert_eq!((0u8), instance_struct.static_dispatch());

    let instance_struct: Struct = Default::default();
    let box_struct: Box<dyn Trait> = Box::new(instance_struct);
    assert_eq!((0), box_struct.static_dispatch());

    let instance_struct: Struct = Default::default();
    let mut v: Vec<&dyn Trait> = Vec::new();
    v.push(&instance_struct);
    for instance_struct in v.iter() {
        assert_eq!((0), instance_struct.static_dispatch());
    }

    let instance_struct: Struct = Default::default();
    let mut v: Vec<Box<dyn Trait>> = Vec::new();
    v.push(Box::new(instance_struct));
    for instance_struct in v.iter() {
        assert_eq!((0), instance_struct.static_dispatch());
    }    
}

调用动态函数的实例

  这里将说明基于衔接特质关键性trait的静态和动态函数实现,但是这静态函数与之前的也是完全不一样的概念。另外将会看到调用这种动态函数,它们看起来是一些更复杂的静态和动态函数。

  下面程序第二段代码的两个函数static_dispatch()dynamic_dispatch(),它们的目的是解决代码重复的相同问题。但是其手段是不同的。

  Rust语言没有继承概念,继承编程不再是软件开发的思想。通过关键词trait定义函数,借助于关键词impl实现函数及其泛型编程方法,以实现多态式编程方法。静态函数static_dispatch()使用了泛型编程方法,关于泛型编程将有另外专题说明。

  Rust语言也通过衔接特质对象及动态调度编程方法,来实现多态式编程方法。动态函数dynamic_dispatch()使用了动态编程方法。特性对象是不限类型的,动态绑定类型是通过实时运行时具体地匹配类型。

  从代码上看,静态函数static_dispatch()和动态函数dynamic_dispatch()都是实现相同的功能。

  下面程序第三段代码里,无论是类型NormalStruct,还是类型TupleStruct,它们都是以同一方式分别调用函数static_dispatch()和函数dynamic_dispatch()

  不管是静态函数,还是动态函数,它们都是基于衔接特质对象的指针实现,这一点是非常重要的。

  下面程序是Rust语言非常经典的代码结构。

// File: examples/trait_dispatch_concrete.rs
// 1. Define and Implement struct, trait and impl..for
struct NormalStruct {
    data: (u8)
}

trait Trait {
    fn _fn(&self) -> (u8);
}

impl Trait for NormalStruct {
    fn _fn(&self) -> (u8) { (self.data) }
}

struct TupleStruct(u8);

impl Trait for TupleStruct {
    fn _fn(&self) -> (u8) { (self.0) }
}

// 2. Implement static and dynamic dispatch
fn static_dispatch<TraitObject: Trait>(r#type: &TraitObject) {
    r#type._fn(); 
}

fn dynamic_dispatch(r#trait: &dyn Trait) {
    r#trait._fn();
}

// 3. Use these dispatch functions
// clear && cargo run --example trait_dispatch_concrete
fn main() {
    let instance_struct = NormalStruct{data: 0};
    assert_eq!((), static_dispatch(&instance_struct));
    assert_eq!((), dynamic_dispatch(&instance_struct));

    let instance_tuple = TupleStruct(0);
    assert_eq!((), static_dispatch(&instance_tuple));
    assert_eq!((), dynamic_dispatch(&instance_tuple));
}

题外话

原始标识符前缀r#

  原始标识符也是一种标识符,其前缀是r#,之后也可以加上任何严格或保留的关键字,但除关键字crate, extern, self, super, Self外。

向量数据类型Vec

  向量数据类型Vec也是一种数组,其内部是以0开始进行排序的,但是这种数组大小是可调整的,或者说是一种连续的且可增长的数组类型。

参考资料