In Rust, can I instantiate my const array without

2020-04-08 11:27发布

问题:

I'm trying to instantiate an array in Rust. Here's one way I could do it at runtime:

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;
}

However, since I'm never going to change the values of this array and I'm going to use the values a lot, I thought this might be a good opportunity to explore the cool stuff going on with the const compile-time evaluation work being done in Rust. I could make it compute the array at compile time and just store the results in the program data so it's ready to go immediately at runtime.

My first step was to create the constant array.

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

Well, this is no good. I've already instantiated it with all zeros. That's not right. Next, I thought maybe I should make a constant function that could instantiate the array.

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
}

And here's where I got stuck. From what I've read, loops inside constant functions are still only on nightly, and I'm trying to stick with stable Rust for the time being to avoid surprises later on. So, where does this leave me? What can I do currently in stable and what's coming down the pipeline in nightly, RFCs, etc.? My next thought was to investigate macros, but I'm not comfortable enough to go down that rabbit hole just yet without knowing if it'll be fruitful. My ultimate goal is to make this array a constant without having to type in 65 values by hand.

回答1:

Cargo supports build.rs files that are compiled and run before the overall compilation. For you, the easiest option would be to use this to generate the table that you would like to use.

The Rust docs have an example for code generation using this method, so if you take your code and use it to generate the array, you should be good to go. You can put build = "build.rs" in your Cargo.toml and have build.rs be:

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(())
}

then you can load that built file.



回答2:

As of now on Rust Stable, this is not possible to do (you need const fn expressions for this to work at compile time).

But fortunately there's an "in-between" solution for this use case (that I have as well quite frequently), which is the lazy_static macro.

Basically it's a lazy-evaluated runtime expression that is computed on first access only.

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

Your code would look this way using this macro:

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 sample link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=61146c5f7de2c9ee1cbcd724dd1a730f

Disclaimer: I'm not the author of lazy_static nor I am related to them in any way.