I wanted to see what the bounds checking generated code looked like, and whether I could get the same assembly when writing my own check. This is what I tried:
// ignore the unbound lifetime, only paying attention to codegen
pub fn manual_bounds_check<'a>(p: *mut u8, l: usize, range: std::ops::Range<usize>) -> &'a mut [u8] {
assert!(range.start < l && range.end <= l && range.start <= range.end);
unsafe {
let start = p.add(range.start);
&mut *std::ptr::slice_from_raw_parts_mut(start, range.len())
}
}
pub fn compiler_bounds_check(p: &mut [u8], range: std::ops::Range<usize>) -> &mut [u8] {
&mut p[range]
}
Manual only generates the code for panicking once (better at least for perf, but means the error you get is less specific)
For #1, is there a way to get better codegen? For #2 maybe the compiler could be tweaked to call a common failure function for both cases that would inspect the registers to figure out which panic message to use? Otherwise every slice[a..b] generates ~4 extra instructions just to tweak the error message you get.
start<l should already be ensured by the combination of end<l and start<=end, so that check seems redundant. Also you seem to be using the wrong pointer (p instead of start) and arguably that manual_bounds_check function should still be marked unsafe.
These things are probably all besides the main point of your question, but I noticed them and I'm leaving the forum for the day.