rustrandomconstants

In Rust is it possible to use a pseudo random number generator with a seed in a const context?


I'm writing a chess engine in Rust and want to start using Zobrist Hashing, which requires me to pre-initialize an array of pseudo-random numbers from a constant seed.

Since this array won't change, I tried to initialize it in a static global array generated at compile time like this:

use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use rand::RngCore;

pub static ZOBRIST_SEEDS: [u64; 781] = generate_zobrist_seeds();

const fn generate_zobrist_seeds() -> [u64; 781] {
    let mut result = [0; 781];
    let mut rng = ChaCha8Rng::seed_from_u64(4174800045971885562);
    let mut i = 0;
    while i < 781 {
        result[i] = rng.next_u64();
        i+= 1;
    }
    result
}

This doesn't work since ChaCha8Rng::seed_from_u64 and ChaCha8Rng::next_u64 are non-constants. Is there any way I could generate this array at compile time, or should I just do it at the start of my runtime, I know the const_random! macro exists, but this doesn't meet my needs since I need the values to stay the same every time the program is built.


Solution

  • First, I implore you consider very carefully: do you actually want a random seed generated at compile time? I would advise against it in most circumstances because of a number of problems, primary of which is it will make your build non-reproducible (there are other blockers in Rust to a reproducible build at the moment, but let's not "send the helve after the hatchet", so to speak).

    Given the particular context you have provided, I would generate the seed at runtime were it to be my decision. To that effect, this is what I would write:

    pub static ZOBRIST_SEEDS: LazyLock<[u64; 781]> = LazyLock::new(|| generate_zobrist_seeds());
    
    // generate_zobrist_seeds needs not be const
    

    This gives you an immutable, lazily initialised runtime value which you can use as if it's a const (technically there is a semantic difference: static is a memory location while const is a value, but in practise using them at the call-site feels very similar).


    On the other hand, if you insist on generating this seed at compile time, you have 3 options. From the least effort to most:

    1. Use comptime crate

    This crate allows you to run arbitrary code at compile time. In effect it relaxes Rust's restriction of "only const functions is const context".

    The downside is you have to introduce a new direct dependency.

    2. Write a custom build script that generates the array as code

    Basically, you generate the string pub const ZOBRIST_SEEDS: [u64; 781] = [ /* ... */ ]; in your build script, write it to a file in OUT_DIR, then include!() it. Here is something I wrote in a public repo that does this: example.

    3. Write your own proc macro

    It's basically #1 but you are hand-rolling everything. Definitely overkill for what you are trying to accomplish. I wouldn't recommend it.