Reputation:
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
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 fn
s, 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:
for
loops in const fn
s 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:
Lazy
accessing the value is only possible at run time.Upvotes: 8
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