The desire for somehow expressing at compile time that a number is in a certain range is clear from the many issues surrounding the topic. Steps are being taken towards a future where they are a reality.
Until we are that far, we can write or generate specific solutions. Let us create a new type that wraps a u32 and ensures that the value inside is always less than 10. We will use this type to index an array of which we know the length to try and skip array bound checks.
pub struct U32Lt10(u32);
impl U32Lt10 {
#[inline]
pub fn new(val: u32) -> Option<Self> {
if val < 10 {
Some(U32Lt10(val))
} else {
None
}
}
#[inline]
pub fn get(&self) -> u32 {
// Try to let the compiler know at the call site that the value
// returned is always < 10.
unsafe {
if self.0 < 10 {
self.0
} else {
std::hint::unreachable_unchecked()
}
}
}
}
pub fn get_it_now(arr: &[u8; 20], idx: U32Lt10) -> u8 {
// We know idx is always less than 10. We also know arr.len() is 20. Thus
// idx is always les than arr.len() and we do not need to check the bounds!
arr[idx.get() as usize]
}
assembly output
playground::get_it_now:
pushq %rax
movl %esi, %eax
movl %esi, %esi
cmpl $19, %eax
ja .LBB0_2
movb (%rdi,%rsi), %al
popq %rcx
retq
.LBB0_2:
leaq .Lanon.a49c2988708a5d4fee9536100b6bda03.0(%rip), %rdi
movl $20, %edx
callq *core::panicking::panic_bounds_check@GOTPCREL(%rip)
ud2
str.0:
.ascii "src/lib.rs"
.Lanon.a49c2988708a5d4fee9536100b6bda03.0:
.quad str.0
.quad 10
.long 30
.long 5
We try very hard in the implementation of U32Lt10::get to let the compiler know that the value it returns is in fact: less than 10. Despite our efforts, when we compile this down to ASM it still performs the check and generates the out of bounds panicking code. Of course we can rewrite the get_it_now function to use the unsafe get_unchecked but we would really rather have the compiler determine that it is safe to omit the check.
I am not familiar with the exact workings of the rust compiler but I think in some places, it supplies llvm range metadata. Just not where we need it.
Does anyone know if what I expect from the compiler is reasonable, why the range meta data is not emitted or if there is a technique to accomplish what I'm trying to do?