1

我是 Rust 的新手。我正在尝试在库中创建一个静态变量DATAVec<u8>以便在编译库后对其进行初始化。然后我将 lib 包含在主代码中,希望DATA直接使用而无需再次调用init_data()。这是我尝试过的:

my_lib.rs:

use lazy_static::lazy_static;

pub fn init_data() -> Vec<u8> {
    // some expensive calculations
}

lazy_static! {
    pub static ref DATA: Vec<u8> = init_data();  // supposed to call init_data() only once during compilation
}

main.rs:

use my_lib::DATA;
call1(&DATA);  // use DATA here without calling init_data()
call2(&DATA);

但事实证明,init_data()仍然在调用中main.rs。这段代码有什么问题?


更新:正如 Ivan C 指出的那样,lazy_static它不在编译时运行。那么,“预加载”数据的正确选择是什么?

4

1 回答 1

2

这里有两个问题:类型的选择和执行分配。

不可能在编译时构造 a Vec、 aBox或任何其他需要堆分配的类型,因为此时堆分配器和堆还不存在。相反,您必须使用引用类型,它可以指向在二进制而不是在运行时堆中分配的数据,或者没有任何引用的数组(如果数据不是太大)。

接下来,我们需要一种方法来执行计算。从理论上讲,最简洁的选择是持续评估——在编译时直接执行部分代码。

static DATA: &'static [u8] = {
    // code goes here
};

然而,在当前稳定的 Rust 版本(我写这篇文章时是 1.58.1)中,常量评估非常有限,因为你不能做任何看起来像dropping 值的事情,或者使用任何属于 trait 的函数。它仍然可以做一些事情,主要是整数运算或构造其他“几乎字面”的数据;例如:

const N: usize = 10;
static FIRST_N_FIBONACCI: &'static [u32; N] = &{
    let mut array = [0; N];
    array[1] = 1;
    let mut i = 2;
    while i < array.len() {
        array[i] = array[i - 1] + array[i - 2];
        i += 1;
    }
    array
};

fn main() {
    dbg!(FIRST_N_FIBONACCI);
}

如果您的计算无法使用 const 评估来表达,那么您将需要以另一种方式执行它:

  • 过程宏实际上是编译器插件,它们可以执行任意计算,但它们的输出是生成的 Rust 语法。因此,程序宏可以使用预先计算的数据生成数组文字。

    程序宏的主要限制是它们必须在专用 crate 中定义(因此,如果您的项目是一个库 crate,现在将改为两个)。

  • 构建脚本是普通的 Rust 代码,可以编译或生成主编译使用的文件。它们不与编译器交互,而是在编译开始之前由 Cargo 运行。

(与 const 评估不同,构建脚本和 proc 宏都不能使用正在构建的 crate 中定义的任何类型或常量;它们可以读取源代码,但是它们运行得太早而无法在其中使用 crate 中的其他项自己的代码。)

在您的情况下,因为您想预先计算一些[u8]数据,我认为最简单的方法是添加一个将数据写入文件的构建脚本,之后您的普通代码可以使用include_bytes!.

于 2022-01-23T17:08:43.030 回答