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.
user16251:
What about
That's Box<A<[T; N]>> => Box<A<[T]>>
, no? (See the edit of my last coment, in case you missed it.)