More musings about slices and arrays

I'll note that re-slicing -- even to the length that it already is -- is fairly commonly done in rust to help LLVM elide bounds checks. (Especially if you're iterating multiple slices; re-slicing both with the same local variable makes it obvious to LLVM that iterating up to that will be in-bounds for both slices.)

If I wanted to do this with indexing, I would write it as

Also, if you really need "make it checked in the caller, where it's obvious" and the function is more complicated than this so you don't want to inline the whole thing, you can do a transformation like this so that the assert gets inlined but the body of the function doesn't:

But really, I think the actual answer for this specific case is that if you need a 10-item prefix, take a &mut [u8; 10], and make the caller give you that. Right now it needs [..10].try_into().unwrap(), but that panic will be trivially removed by LLVM, and eventually we'll have bounds on const generics so that if you're starting with a [u8; 100] you can get a [u8; 10] prefix infallibly.


EDIT: A simple example of this: x[1] + x[0] generates fewer bounds checks than x[0] + x[1], because of the order they happen and the fact that the bounds error has the index in it. So this is a great example of how adding an assert! at the top of the method can actually make it faster: assert!(x.len() >= 3); x[0] + x[1] + x[2] gets way nicer codegen than the version without the assert. So if you're doing a bunch of indexing, I highly recommend adding an assert at the top when you have something reasonable to check. It means a nicer error for a bad caller and usually-better codegen.

3 Likes