【问题标题】:In Rust, can I instantiate my const array without hard-coding in the values? Compile-time evaluation?在 Rust 中,我可以在不硬编码值的情况下实例化我的 const 数组吗?编译时评估?
【发布时间】:2020-03-26 01:27:12
【问题描述】:

我正在尝试在 Rust 中实例化一个数组。这是我可以在运行时执行此操作的一种方法:

let mut t = [0_u32; 65];
for i in 0..t.len() {
    t[i] = ((i as f64).sin().abs() * 2.0_f64.powf(32.0)).floor() as u32;
}

但是,由于我永远不会更改这个数组的值,而且我会经常使用这些值,我认为这可能是一个很好的机会来探索 const 的精彩内容在 Rust 中完成的编译时评估工作。我可以让它在编译时计算数组并​​将结果存储在程序数据中,以便在运行时立即运行。

我的第一步是创建常量数组。

const T: [u32; 65] = [0; 65];

嗯,这不好。我已经用全零实例化了它。那是不对的。接下来,我想也许我应该创建一个可以实例化数组的常量函数。

const fn sine_table() -> [u32; 65] {
    let mut t = [0_u32; 65];
    let mut i = 0;


    loop {
        if i > 65 {
            break;
        }

        // Do the math...
    }

    t
}

这就是我卡住的地方。根据我的阅读,常量函数中的循环仍然只在夜间运行,我暂时尝试使用稳定的 Rust,以避免以后出现意外。那么,这将把我留在哪里?我目前可以在稳定状态下做什么,以及在 nightly、RFC 等方面的进展如何?我的下一个想法是研究宏,但我现在还不够舒服,不能在不知道它是否会取得成果的情况下进入那个兔子洞。我的最终目标是让这个数组成为一个常量,而不必手动输入 65 个值。

【问题讨论】:

  • 你考虑过构建脚本吗? doc.rust-lang.org/cargo/reference/…
  • 目前您甚至无法在夜间或任何时间运行此程序,no f64 functions are const fn 因为it can vary between platforms。我个人会包含某种文件,或者制作一个可以接受动态表达式并在编译时评估它们的 proc-macro。
  • @OptimisticPeach 你能给我一个可以在编译时评估动态表达式的 proc-macro 的小例子吗?
  • 在这个用例中,构建脚本比 proc 宏要容易得多。
  • @loganfsmyth 如果您想要功劳,请添加您的构建脚本建议作为答案。我最终使用的是你的。

标签: arrays rust constants compile-time


【解决方案1】:

截至目前,在 Rust Stable 上,这是不可能的(您需要 const fn 表达式才能在编译时工作)。

但幸运的是,此用例有一个“中间”解决方案(我也经常使用),即lazy_static 宏。

基本上,它是一个惰性求值的运行时表达式,仅在首次访问时计算。

https://docs.rs/lazy_static/1.4.0/lazy_static/

你的代码使用这个宏会是这样的:

use lazy_static::lazy_static;

const SINE_TABLE_SIZE: usize = 65;
lazy_static! {
    pub static ref SINE_TABLE: [u32; SINE_TABLE_SIZE] = {
        let mut table = [0_u32; SINE_TABLE_SIZE];
        for i in 0..SINE_TABLE_SIZE {
            table[i] = ((i as f64).sin().abs() * 2.0f64.powf(32.)).floor() as u32;
        }

        table
    };
}

Rust Playground 示例链接:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=61146c5f7de2c9ee1cbcd724dd1a730f

免责声明:我不是lazy_static 的作者,也不与他们有任何关系。

【讨论】:

  • 这是一个完全合理和明智的解决方案,它可能是大多数人想要的。但是,我很奇怪,我选择不在我的项目中使用任何 crate,所以我选择了货物构建文件功能。但我给了你一个赞成票,因为你的回答是一个理智的人应该做的。
  • @dynamitereed 谢谢!老实说,自从我是一个优化狂人以来,我就考虑过 build.rs 解决方案,但是对于 65 大小的 sin 表,我认为这有点矫枉过正。如果您的尺寸更大,尽管将其作为构建步骤非常有意义,以防止在首次访问时进行计算。
【解决方案2】:

Cargo 支持在整体编译之前编译和运行的build.rs 文件。对您来说,最简单的选择是使用它来生成您想要使用的表。

Rust 文档有一个code generation using this method 的示例,因此如果您使用您的代码并使用它来生成数组,那么您应该很高兴。您可以将build = "build.rs" 放入您的Cargo.toml 并让build.rs 成为:

use std::io::{Result, Write};

fn main() -> Result<()> {
  let out_dir = env::var("OUT_DIR").unwrap();
  let dest_path = Path::new(&out_dir).join("sin_abs_const.rs");
  let mut f = File::create(&dest_path).unwrap();

  write!(f, "const T: [u32; 65] = [\n")?;
  for i in 0..64 {
    write!(f, "  {},\n", ((i as f64).sin().abs() * 2.0_f64.powf(32.0)).floor() as u32)?;
  }
  write!(f, "];\n")?;

  Ok(())
}

然后您可以加载该构建文件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-16
    • 2020-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-13
    • 1970-01-01
    相关资源
    最近更新 更多