Is there a way in Rust to limit paramter values to a range or condition depending on a generic type?
Given a T
as unsigned, u8
or u16
const fn do_something_with_a_bit_offset<T>(offset: u8) -> T {
// .. some ops return a T ..
}
If T
is u8
, offset can be 0..7, offset > 7 should not compile
If T
is u16
, offset can be 0..15, offset > 15 should not compile
let mask = do_something_with_a_bit_offset::<u8>(0); // OK
let mask = do_something_with_a_bit_offset::<u8>(8); // Compile error
let mask = do_something_with_a_bit_offset::<u16>(0); // OK
let mask = do_something_with_a_bit_offset::<u16>(8); // OK
let mask = do_something_with_a_bit_offset::<u16>(16); // Compile error
How can I do this at compile time?
Before we start, I must mention the obvious: if offset
is not a compile-time constant, there is no way to avoid a runtime check.
If the offset
parameter is guaranteed to be a compile-time constant, you can use const generics and some associated constant magic:
trait CheckOffset<const OFFSET: usize> {
const CHECKED_OFFSET: usize;
}
impl<T: MaxOffset, const OFFSET: usize> CheckOffset<OFFSET> for T {
const CHECKED_OFFSET: usize = if OFFSET <= T::MAX_OFFSET {
OFFSET
} else {
panic!("Invalid offset") // Compile time panic
};
}
CheckOffset
is a helper trait that defines an associated constant CHECKED_OFFSET
which (in its implementation) actually does the offset check. A reference to <T as CheckOffset<OFFSET>>::CHECKED_OFFSET
will compile only if the given offset OFFSET
is less than the maximum offset for T
(<T as MaxOffset>::MAX_OFFSET
). MaxOffset
is a helper trait that defines the maximum bit offset for a type:
trait MaxOffset {
const MAX_OFFSET: usize;
}
impl<T> MaxOffset for T {
const MAX_OFFSET: usize = 8 * size_of::<T>() - 1;
}
If you need a function to take in a compile-time checked offset for a given type T
, you must
OFFSET
) to represent the input offsetCheckOffset<OFFSET>
constraint to the type T
<T as CheckOffset<OFFSET>>::CHECKED_OFFSET
as the actual offset. If you do not use the CHECKED_OFFSET
constant, the max offset check in the associated constant implementation will not be evaluated.fn foo<T: CheckOffset<OFFSET> + 'static, const OFFSET: usize>() {
let offset = T::CHECKED_OFFSET;
println!("{} Offset: {:?}", type_name::<T>(), offset);
}
fn main() {
foo::<u8, 0>();
foo::<u8, 7>();
// foo::<u8, 8>(); // Compile error
foo::<u16, 0>();
foo::<u16, 15>();
// foo::<u16, 16>(); // Compile error
foo::<u32, 0>();
foo::<u32, 31>();
// foo::<u32, 32>(); // Compile error
}
This approach will work for any compile-time const-evaluatable constraint you may have.