Can const generic parameters be erased?

Is there any way in Rust to erase const generic parameters? That is, if I have some type

struct A<const B: usize> {
    // ...
}

is there something analogous to dyn Trait that would let me construct and somehow store an A<B> where B is only known at runtime and not specified as part of the type? For arrays [T; N] we have slices [T]. Of course we could build an enumeration with A<0>, A<1>, etc. but is there a better way? Does creating a value of type A<B> always monomorphize the parameter B?

Yeah, this is fundamental to how generics work. A is really a type factory, not a type itself.

You can just use a normal trait for this:

pub struct A<const B: usize> {
    pub b: [u8; B],
}

impl<const B: usize> AsRef<[u8]> for A<B> {
    fn as_ref(&self) -> &[u8] {
        &self.b
    }
}

Or you can make your type generic over more types.

pub struct A<B: ?Sized> {
    pub b: B,
}

impl<B: AsRef<[u8]>> AsRef<[u8]> for A<B> {
    fn as_ref(&self) -> &[u8] {
        self.b.as_ref()
    }
}

This allows you to make Box<A<[u8]>> as well as A<[u8; 5]>.

3 Likes

The only way I know of is to implement an (object safe) trait for A, say GetB, and coerce to &dyn GetB:

struct A<const B: usize> {}

trait GetB {
    fn get_b(&self) -> usize;
}

impl<const B: usize> GetB for A<B> {
    fn get_b(&self) -> usize {
        B
    }
}

let a = A::<2> {};

let a_dyn: &dyn GetB = &a;
println!("{}", a_dyn.get_b());

playground

1 Like

dyn Trait is a way to do it.

(Edit)

Unsizing coercion is another possibility, like @drewtato said. Note how you need B: ?Sized on the parameter.

pub fn unsize<T, const N: usize>(a: A<[T; N]>) -> Box<A<[T]>> {
    Box::new(a)
}
2 Likes

A is, but does it have to be that way? The compiler could transform

fn f<const N: usize>(...)

into

fn g(N: usize, ...)

turning a generic parameter into just a parameter.

With dyn Trait unsizing coercion gives me a constructor where the type doesn't appear in the target. Rust gives me functions

T: Trait => Box<T> -> Box<dyn Trait>
const N: usize => Box<[T; N]> -> Box<[T]>

What about

const N: usize => Box<A<B>> -> Box<some unsized version of A>

You could widen a reference &A<B> to include B as the second word (if A is Sized). Is this completely impractical?

It's practical, just not something in current Rust. The current solutions are, for the most part, deemed sufficient.

It can't because parameter values (like in g) aren't known at complie time. f is how you write the "known at compile time" version.

That's Box<A<[T; N]>> => Box<A<[T]>>, no? (See the edit of my last coment, in case you missed it.)