栈上动态分发

描述

我们可以对多个值进行动态分发,然而,要做到这一点,我们需要声明多个变量来绑定不同类型的对象。 为了根据需要延长生命周期,我们可以使用延迟条件初始化,如下所示:

例子

use std::io;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let arg = "-";

    // These must live longer than `readable`, and thus are declared first:
    let (mut stdin_read, mut file_read);

    // We need to ascribe the type to get dynamic dispatch.
    let readable: &mut dyn io::Read = if arg == "-" {
        stdin_read = io::stdin();
        &mut stdin_read
    } else {
        file_read = fs::File::open(arg)?;
        &mut file_read
    };

    // Read from `readable` here.

    Ok(())
}

动机

Rust默认会对代码进行单态处理。这意味着每一种类型的代码都会被生成一个副本,并被独立优化。 虽然这允许在热点路径上产生非常快的代码,但它也会在性能不重要的地方使代码变得臃肿,从而耗费编译时间和缓存使用量。

幸运的是,Rust允许我们使用动态分发,但我们必须明确要求它。

优势

我们不需要在堆上分配任何东西。 我们也不需要初始化一些我们以后不会用到的东西,也不需要把下面的整个代码单一化,以便FileStdin一起工作。

劣势

该代码需要比基于Box的版本有更多的移动语义部分。

// We still need to ascribe the type for dynamic dispatch.
let readable: Box<dyn io::Read> = if arg == "-" {
    Box::new(io::stdin())
} else {
    Box::new(fs::File::open(arg)?)
};
// Read from `readable` here.

讨论

Rust新手通常会了解到,Rust要求所有变量在使用前被初始化,所以很容易忽略这样一个事实,即未使用的变量很可能是未初始化的。 Rust非常努力地确保这一点,而且只有初始化过的值在其作用域的末端被丢弃。

这个例子符合Rust对我们的所有约束:

  • 所有的变量在使用(本例中为借用)之前都被初始化。
  • 每个变量只持有单一类型的值。在我们的例子中,stdinStdin类型,fileFile类型,readable&mut dyn Read类型。
  • 每个被借用值的生命周期都比它的所有借用引用要久。

参见

Latest commit a152399 on 21 Apr 2021