Slice::split_at should have an Option variant

slice::split_at is panicky, which is a footgun in code that attempts to be panic-free. It would be better if there were a version of this function that returned an Option instead.

I have the following helper function that I use sometimes:

/// Like slice::split_at but returns an Option instead of panicking
#[inline]
fn maybe_split_at(slice: &[u8], mid: usize) -> Option<(&[u8], &[u8])> {
    if mid > slice.len() {
        None
    } else {
        // Note: We're trusting the compiler to inline this and remove the assertion
        // hiding on the top of slice::split_at: `assert(mid <= self.len())`
        Some(slice.split_at(mid))
    }
}

What would this be called?

  • slice::split_at_option
  • slice::try_split_at
  • slice::maybe_split_at
  • slice::splito_at
20 Likes

checked_split_at would be consistent with checked_add et al. try_split_at would hint returning a Result in the current language but this might change once the generic Try trait is stabilized. maybe_* or *_opt don't have precedent in std.

10 Likes

Another motivation: There is no panic-free way to do this with mutable slices without unsafe. For split_at you can at least do slice.get(..mid).zip(slice.get(mid..)). Might be relevant for the "absolutely no panic" flavour that the linux guys want.

There's also https://doc.rust-lang.org/nightly/std/primitive.slice.html#method.split_at_mut_unchecked, so checked, normal, and unchecked might make people think it's like add where whether the normal one panics depends on debug-vs-release.

Well, if you just add a bounds check before hand then it's panic-free...

1 Like

An interesting option could be .get_split_at, leaning on .get(_)/[_] being the panicking split for normal indexing.

Another potentially interesting option could be

fn <[T]>::split_at_clamped(&self, mid: usize) -> (&[T], &[T]) {
    self.split_at(self.len().min(mid))
}

(or whatever bikeshed). Returning Option is perhaps a nicer API since if it gives Some you know the first slice has mid elements, but there's less risk of the checked/unchecked/(none) split clashing with .get being .get_checked (and panicking being []).

2 Likes

I was considering calling this split_off but I'm glad I didn't since Vec::split_off exists (but I only checked the slice docs thinking the name was familiar rather than searching the whole docs) and also panics if the index is out of bounds.

Whatever is used for split_at should probably be used for split_off as well (and for std, that might just be saying to check the length manually).

From a Clippy POV, it would additionally be useful to have an Option variant so our slice indexing lints have a useful direction to suggest fixes in.

2 Likes

Galaxy brain: Bring add to parity with this by creating a fn add_unchecked that makes overflow UB :smiling_imp:

That already exists on nightly :smile: