I'm working on a library where I want to select the smallest unsigned integer type based on a const generic value at compile time.
For example, I want to define something like this:
type T = MinType<255>::Type; // T should be u8
type T = MinType<256>::Type; // T should be u16
type T = MinType<70000>::Type; // T should be u32
In other words, I'd like to define a generic helper (e.g., a trait or struct) such that:
MinType::<N>::Type
expands to the smallest unsigned integer type that can hold all values from 0 to N, for any given const N: usize.
Is this kind of type selection possible in stable or nightly Rust? Or does it require macros, nightly features, or external crates?
With #![feature(generic_const_exprs)]
it's easily achievable:
#![feature(generic_const_exprs)]
struct MyType<const N: u128>;
trait ConstGenericType {
type Type;
}
struct MyLogType<const N: u128>;
impl ConstGenericType for MyLogType<8> {
type Type = u8;
}
impl ConstGenericType for MyLogType<16> {
type Type = u16;
}
impl ConstGenericType for MyLogType<32> {
type Type = u32;
}
impl ConstGenericType for MyLogType<64> {
type Type = u64;
}
impl ConstGenericType for MyLogType<128> {
type Type = u128;
}
impl<const N: u128> ConstGenericType for MyType<N>
where
MyLogType<{ required_bits(N) }>: ConstGenericType,
{
type Type = <MyLogType<{ required_bits(N) }> as ConstGenericType>::Type;
}
const fn required_bits(n: u128) -> u128 {
if n < (1 << 8) {
8
} else if n < (1 << 16) {
16
} else if n < (1 << 32) {
32
} else if n < (1 << 64) {
64
} else {
128
}
}
type T8 = <MyType<{ u8::MAX as u128 }> as ConstGenericType>::Type;
type T16 = <MyType<{ u16::MAX as u128 }> as ConstGenericType>::Type;
type T32 = <MyType<{ u32::MAX as u128 }> as ConstGenericType>::Type;
type T64 = <MyType<{ u64::MAX as u128 }> as ConstGenericType>::Type;
type Tword = <MyType<{ usize::MAX as u128 }> as ConstGenericType>::Type;
type T128 = <MyType<{ u128::MAX }> as ConstGenericType>::Type;
fn main() {
dbg!(std::any::type_name::<T8>());
dbg!(std::any::type_name::<T16>());
dbg!(std::any::type_name::<T32>());
dbg!(std::any::type_name::<T64>());
dbg!(std::any::type_name::<Tword>());
dbg!(std::any::type_name::<T128>());
}
The trait is necesseary for an associated type and to be able to declare a bound that enforces the existence for a ::Type
on the select values 8, 16, 32, 64 and 128, with an inherent implementation the compiler can't see that it's actually available for all return values of required_bits
. generic_const_exprs
is required to convert the const generic to the number of bytes required. You could use a macro to add the ConstGenericType
implementations all at once.