Const &'static [A] -> const &'static [B]?

pub struct Foo {}

impl A {
  pub fn to_b(&self) -> B;
}

impl Foo {
  const Const_A: &'static [A] = blahblahblah ;
  const Const_B: &'static [B] = ... ;
}

Given the above, is there a way, at COMPILE TIME, to "map/iter" over Const_A to call .to_b to generate Const_B ?

I need a solution that works on Rust stable, though I am happy to hear about solutions that only works on unstable/nightly too.

EDIT: I'm pretty sure I can solve this via macros. I am interested in hearing non-macro solutions. (In case situations where Const_B is defined in a different struct / file).

first of all, your function A::to_b() must be a const function, i.e.

impl A {
-  pub fn to_b(&self) -> B;
+  pub const fn to_b(&self) -> B;
}

as far as I know, I don't think it's possible in stable rust (in general), but it's possible if B satisfy some condition, (or it's always possible with unstable)

the key is, to get a const slice, you must have a const array. this is done typically by an array literal. but in your case, you must generate the array by calling a function. although the function to generate a single B can be const, none of the methods (that I know of) to populate an array of values can be evaluated in const context in stable, these includes array::from_fn, array::map.

if your type B has some "default" value as placeholders, you can create an array of the default values and then fill in with the calculated value, something like:

    const Const_B: &'static [B] = {
        const N: usize = Foo::Const_A.len();
        const BS: [B; N] = {
            const PLACEHOLDER: B = todo!();
            let mut bs = [PLACEHOLDER; N];
            let mut i = 0;
            while i < N {
                bs[i] = Foo::Const_A[i].to_b();
                i += 1;
            }
            bs
        };
        &BS
    };

please note, you cannot use for loops in const context, because for loops desugar to IntoIter.

in general, if type B cannot provide a constant placeholder value, you can use MaybeUninit, but you'll need to enable a bunch of unstable features, I think at least the following are required, but there may be more

// needed to create a uninitialized array
#![feature(maybe_uninit_uninit_array_transpose)]
// needed to overwrite the uninitialized memory with a value
#![feature(const_maybe_uninit_write)]

the code should be similar, but you need an additional transmute, something like:

    const Const_B: &'static [B] = {
        const N: usize = Foo::Const_A.len();
        const BS: [B; N] = {
            let mut bs: [MaybeUninit<B>; N] = MaybeUninit::uninit().transpose();
            let mut i = 0;
            while i < N {
                bs[i].write(Foo::Const_A[i].to_b());
                i += 1;
            }
            unsafe { std::mem::transmute(bs) }
        };
        &BS
    };
2 Likes

This is definitely my mistake. Good call/fix.

Note that you can initialize an array of MaybeUninit in stable by having a const of type MaybeUninit and then using the usual array initialization with it.

write is also the same as assigning MaybeUninit::new(value), which is const on stable.

2 Likes

This is a surprising solution so I am trying to reverse engineer the thought process. Does it go something like this:

  1. to get a slice, we need to either slice some variable or slice a literal

  2. setting aside macros, we can't do the literal solution, so we have to slice a variable

  3. so now we have to slice an array or slice a vec

  4. we can't get vecs at comptime, so we can only slice an array

  5. arrays will require that we alloc/init N items at once

  6. so either we need B::default() or MaybeUninit<...>

  7. then your two solutions naturally arises from working out the technical details/implementations of both approaches

If there are any other possible "routes" I should have considered above, I am interested in hearing about those too.

your reasoning is exactly how I come up with the solution.

to my knowledge, the only thing you can do differently is how you create the constant array. I don't think you can get a constant slice without create a constant array first.

generally, to create a slice of some type, you have two possible ways to go

  1. the safe way: create the slice by slicing an array, a single object, or sub-slicing another slice;

  2. the unsafe way: transmute from a slice of different type (you must hold the safety requirement for the transmutation to be sound, e.g. they must have compatible memory layout etc.), (I would classify creating a slice from "raw" parts is the same as transmutation)

in your problem, you want a constant slice that depends on another slice, even if you want to go the unsafe way (i.e. transmutation), essentially you still need create a slice of some other type (which has compatible memory layout of B), which is essentially the same problem to begin with.

so I don't think you can skip the step of creating a constant array. (except for the "trivial" cases, e.g., your A and B have compatible layout, and you can transmute the slice of A into a slice of B directly)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.