user10060259
user10060259

Reputation:

const arrays in rust - initialise with list comprehension?

Is there a way to use closures or list comprehension when defining constant arrays in Rust? Suppose we have these three const arrays:

const K1 : [i32;8] = [100, 100, 101, 102, 104, 106, 108, 900];
const K2 : [i32;8] = [-100, -100, -101, -102, -104, -106, -108, -900];
const K3 : [i32;8] = [-900, -108, -106, -104, -102, -101, -100, -100];

In python K2 and K3 could be expressed as

K2=[-x for x in K1]
K3=[-x for x in K1[::-1]

Rust has the cute crate which emulates python list comprehension, but I don't think it can be used to define constants. So is there a more elegant solution than simply typing out K2 and K3 as above?

Upvotes: 3

Views: 1640

Answers (2)

Kevin Reid
Kevin Reid

Reputation: 43872

Someday, when Rust has more constant-evaluation abilities, we may be able to write the following:

const K1: [i32; 8] = [100, 100, 101, 102, 104, 106, 108, 900];
const K2: [i32; 8] = K1.map(|x| -x);
const K3: [i32; 8] = { let mut a = K2; a.reverse(); a };

However, for now, neither array::map() nor slice::reverse() are const fns, so you can't use them in constants. map in particular is going to be more trouble because it requires the ability to define higher-order const fns which is not yet available.

However, we can define our own const functions to do the specific jobs we need. This requires a lot more code, so it's not a great idea if you have only one case, but it could be worthwhile anyway to help readability of the constant definitions, or if these situations come up more than once.

const K1: [i32; 8] = [100, 100, 101, 102, 104, 106, 108, 900];
const K2: [i32; 8] = array_i32_mul(-1, K1);
const K3: [i32; 8] = array_reverse(K2);

const fn array_i32_mul<const N: usize>(factor: i32, mut a: [i32; N]) -> [i32; N] {
    let mut i = 0;
    while i < N {
        a[i] *= factor;
        i += 1;
    }
    a
}

const fn array_reverse<T: Copy, const N: usize>(mut a: [T; N]) -> [T; N] {
    let mut i = 0;
    while i < N / 2 {
        let from_end = N - i - 1;
        (a[i], a[from_end]) = (a[from_end], a[i]);
        i += 1;
    }
    a
}

fn main() {
    dbg!(K1, K2, K3);
}

Notes:

  • It's not possible to use for loops in const fns yet, because traits aren't supported and for uses Iterator which is a trait, so we have to use while for all our indexing.
  • array_reverse would not need T: Copy if std::mem::swap() were const, but it isn't yet.

The advantages of going to this effort over using Lazy, as suggested in another answer, are:

  • The constants can be used in situations where the numeric values are actually required to be constants — with Lazy accessing the value is only possible at run time.
  • Accessing the values doesn't require a run-time check for whether they've been initialized yet.
  • The type is an array type, rather than something that merely derefs to an array.

Upvotes: 8

prog-fh
prog-fh

Reputation: 16910

I would try with once_cell

I don't find this as elegant as list-comprehensions, but maybe it's enough for the intended usage.

use once_cell::sync::Lazy;

const K1: [i32; 8] = [100, 100, 101, 102, 104, 106, 108, 900];

const K2: Lazy<[i32; K1.len()]> = Lazy::new(|| {
    let mut k2 = [0; K1.len()];
    k2.iter_mut().zip(K1.iter()).for_each(|(x2, x1)| *x2 = -*x1);
    k2
});

const K3: Lazy<[i32; K1.len()]> = Lazy::new(|| {
    let mut k3 = [0; K1.len()];
    k3.iter_mut().zip(K1.iter().rev()).for_each(|(x3, x1)| *x3 = -*x1);
    k3
});

fn main() {
    println!("{:?}", K1);
    println!("{:?}", *K2);
    println!("{:?}", *K3);
}
/*
[100, 100, 101, 102, 104, 106, 108, 900]
[-100, -100, -101, -102, -104, -106, -108, -900]
[-900, -108, -106, -104, -102, -101, -100, -100]
*/

Upvotes: 1

Related Questions