Sitelet https://github.com/rust-lang/rust/issues/90091
Skip to content

Tracking Issue for split_array #90091

Description

@jethrogb

View all comments

Feature gate: #![feature(split_array)]

This is a tracking issue for splitting slices and arrays into constant-length arrays.

Public API

impl<T, const N: usize> [T; N] {
    pub fn split_array_ref<const M: usize>(&self) -> (&[T; M], &[T]);
    pub fn split_array_mut<const M: usize>(&mut self) -> (&mut [T; M], &mut [T]);

    pub fn rsplit_array_ref<const M: usize>(&self) -> (&[T], &[T; M]);
    pub fn rsplit_array_mut<const M: usize>(&mut self) -> (&mut [T], &mut [T; M]);
}

Similar functions for slices have already been stabilized as:

impl [T] {
    pub const fn split_first_chunk<const N: usize>(&self) -> Option<(&[T; N], &[T])>;
    pub fn split_first_chunk_mut<const N: usize>(&mut self) -> Option<(&mut [T; N], &mut [T])>;
    pub const fn split_last_chunk<const N: usize>(&self) -> Option<(&[T], &[T; N])>;
    pub fn split_last_chunk_mut<const N: usize>(&mut self) -> Option<(&mut [T], &mut [T; N])>;
}

See #111774

Unresolved Questions

impl<T, const N: usize> [T; N] {
    pub fn split_array<const M: usize>(self) -> ([T; M], [T; N - M]);
    pub fn split_array_ref<const M: usize>(&self) -> (&[T; M], &[T; N - M]);
    pub fn split_array_mut<const M: usize>(&mut self) -> (&mut [T; M], &mut [T; N - M]);
}

However, const generics is not powerful enough for this today. See #83233 (comment) and #83233 (comment).

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    T-libs-api[DEPRECATED; DO NOT USE]
    on Oct 20, 2021
  2. jdahlstrom commented on Oct 28, 2021

    @jdahlstrom

    Hmm. Panicking if M > slice.len(), rather than returning a Result/Option, seems a bit surprising to me. I guess it's consistent with split_at, but on the other hand inconsistent with eg. split_first.

  3. scottmcm commented on Dec 3, 2021

    @scottmcm
    Member

    For the slice versions of these, I think it's important that they not only offer -> (&[T; N], &[T]), but also -> (&[T], &[T; N]). (This is basically the same as how there's both as_chunks and as_rchunks, see #78818.)

    That might open up new naming possibilities too. To spitball:

    • split_prefix: &[T] -> (&[T; N], &[T]) and
    • split_suffix: &[T] -> (&[T], &[T; N])
  4. jplatte commented on Dec 4, 2021

    @jplatte
    Contributor

    I agree that should exist, but I think it should be named rsplit_array since there's already a bunch of split functions whose "from the end" variations are all called rsplit*.

  5. yaahc commented on Dec 10, 2021

    @yaahc
    Member

    I'd argue for local consistency over global consistency. In the case of slice we already have methods following both conventions, but the methods following the _prefix _suffix convention are related to stripping, where as the split methods all currently follow the split rsplit convention, so I lean towards keeping the current names.

  6. added a commit that references this issue on Dec 11, 2021
  7. jethrogb commented on Dec 27, 2021

    @jethrogb
    ContributorAuthor

    Should there also be a shorthand API for slice[a..].split_array_ref::<B>() (the array version of &slice[a..(a+B)])?

  8. pmnoxx commented on Jan 16, 2022

    @pmnoxx
    Contributor

    I think that the result should be an Option. This would allow us to properly handle splitting in case, the number of elements is lesser than the given contant.

    impl<T, const N: usize> [T; N] {
        pub fn split_array_ref<const M: usize>(&self) -> Option<(&[T; M], &[T])>;
        pub fn split_array_mut<const M: usize>(&mut self) -> Option<(&mut [T; M], &mut [T])>;
    
        pub fn rsplit_array_ref<const M: usize>(&self) -> Option<(&[T], &[T; M])>;
        pub fn rsplit_array_mut<const M: usize>(&mut self) -> Option<(&mut [T], &mut [T; M])>;
    }

    I've been looking an api like that. However, it would be great to add a proper handling of cases, where split can't happen.

    Consider

    [1,2,3].split_array_ref<4>().is_none(); /// Would make sense if the split fails
    
    /// It's useful for network code, to check if prefix has length at least 4 to decode the message.
    /// This code can avoid any unwraps, because `u32::from_le_bytes` need array of 4 elements as input. 
    if let Some((left, _) = [1,2,3,4,5].split_array_ref<4>() {
       println!("{}", u32::from_le_bytes(left));
    }
  9. jethrogb commented on Jan 16, 2022

    @jethrogb
    ContributorAuthor

    split_at doesn't return an Option.

  10. pmnoxx commented on Jan 16, 2022

    @pmnoxx
    Contributor

    split_at doesn't return an Option.

    Yes, that's unfortunate. I think it would be great to add try_split_at api. This would simplify at lot of code.

    impl<T, const N: usize> [T; N] {
       pub fn try_split_at(&self, mid: usize) -> Option<&[T], &[T]>
    
        pub fn try_split_array_ref<const M: usize>(&self) -> Option<(&[T; M], &[T])>;
        pub fn try_split_array_mut<const M: usize>(&mut self) -> Option<(&mut [T; M], &mut [T])>;
    
        pub fn try_rsplit_array_ref<const M: usize>(&self) -> Option<(&[T], &[T; M])>;
        pub fn try_rsplit_array_mut<const M: usize>(&mut self) -> Option<(&mut [T], &mut [T; M])>;
    }
    

    For example:
    Currently, I have to write code like this to use the api:

    let ary = [127,0,0,1,55,66];
    
    if ary.len() >= 4 {
       let (ip,_) = ary.split_at(4);
       ///. ...
    }

    I think it would be much cleaner to write it in a declarative way, which avoid hidden unwraps of getting a slice.

    let ary = [127,0,0,1,55,66];
    
    if let Some((ip,_)) = ary.try_split_at(4) {
      /// ...
    }

    Or even better

    let ary = [127,0,0,1,55,66];
    
    if let Some((ip,_)) = ary.try_split_array_ref<4>() {
      /// This makes ip a reference to 4 bytes array.
      /// ...
    }
  11. scottmcm commented on Mar 22, 2022

    @scottmcm
    Member

    cc #95198, which proposes doing this more like the split_first family of things.

    Those naturally would return Option, the same way that first & split_first return Option today.

  12. faern commented on Apr 7, 2022

    @faern
    Contributor

    I definitely want infallible (fail at compile time) split methods on arrays. I want to be able to cut arrays up into smaller array chunks without any bounds checks at runtime. I know there are issues currently that won't allow this API today. But I think we should aim for a future where we can do that.

    Being able to parse the content of arrays without paying more bounds checks or code bloat than needed is definitely desired both for readability and performance. ([0u8; 100]).split_array_ref::<10>().expect("out of bounds, will never happen) does not look nice and has a lot of code paths that we already know won't happen. If it was a compile time error we would not accidentally create bugs where the 10 was made into a too large number.

  13. faern commented on Apr 7, 2022

    @faern
    Contributor
    impl<T, const N: usize> [T; N] {
        pub fn try_split_array_ref<const M: usize>(&self) -> Option<(&[T; M], &[T])>;
    }

    This makes no sense IMO 🤔 Because the only time it would ever be None should be found at compile time. I know that's not possible now. But why introduce a large API surface and use up a bunch of good method names for something that's (hopefully)? a temporary limitation.

  14. Xiretza commented on May 18, 2022

    @Xiretza
    Contributor

    I agree with the comments above that the methods on &[T] should return Options, but as @faern said this makes no sense on arrays, where all size checks are known at compile time. In my opinion the API would ideally look like this (note also the rsplit_ methods on arrays, for completeness):

    impl<T, const N: usize> [T; N] {
        pub fn split_array<const M: usize>(self) -> ([T; M], [T; N - M]);
    
        pub fn split_array_ref<const M: usize>(&self) -> (&[T; M], &[T; N - M]);
        pub fn split_array_mut<const M: usize>(&mut self) -> (&mut [T; M], &mut [T; N - M]);
    
        pub fn rsplit_array_ref<const M: usize>(&self) -> (&[T; N - M], &[T; M]);
        pub fn rsplit_array_mut<const M: usize>(&mut self) -> (&mut [T; N - M], &mut [T; M]);
    }
    
    impl<T> [T] {
        pub fn split_array_ref<const N: usize>(&self) -> Option<(&[T; N], &[T])>;
        pub fn split_array_mut<const N: usize>(&mut self) -> Option<(&mut [T; N], &mut [T])>;
    
        pub fn rsplit_array_ref<const N: usize>(&self) -> Option<(&[T], &[T; N])>;
        pub fn rsplit_array_mut<const N: usize>(&mut self) -> Option<(&mut [T], &mut [T; N])>;
    }

    But as noted in the OP, this requires more advanced const generics features. The alternative array API (returning slices for the "rest") would be possible today, but I think it'd be better to wait until the "proper" solution is possible rather than stabilizing something suboptimal.

  15. 23 remaining items

  16. scottmcm commented on Jan 11, 2024

    @scottmcm
    Member

    With those methods in FCP (#117561 (comment)), nominating this one for libs-api to consider whether they'd like to see these ones deleted from nightly.

    Err, I should read better. https://github.com/rust-lang/rust/pull/117561/files#diff-e8ccaf64ce21f955ccebef33b52158631493a6f0966815a2ebc142d7cd2b5e06L2024 is removing these methods.

  17. linked a pull request that will close this issueStabilize `slice_first_last_chunk` #117561on Jan 11, 2024
  18. added a commit that references this issue on Jan 19, 2024
  19. added a commit that references this issue on Jan 20, 2024
  20. tgross35 commented on Jan 20, 2024

    @tgross35
    Member

    This should be reopened, I'm not sure why #117561 closed it. Also the tracking issue can be updated to remove the impl<T> [T] block, #117561 removed it (and stabilized the slice_first_last_chunk in its place, yay!)

  21. scottmcm commented on Jan 20, 2024

    @scottmcm
    Member

    Oh, that's my fault -- I saw that it removed split_array_ref and such and thought it was everything.

  22. tgross35 commented on Jan 20, 2024

    @tgross35
    Member

    Oh, no I only removed the slice components. I know we want a different API but figured that would just be a rework of this implementation.

    But maybe it’s better to start something new with a clean tracking history. I can submit a PR to remove if so.

  23. tgross35 commented on Jan 21, 2024

    @tgross35
    Member

    Haven't seen it linked here, the arrayref crate provides a way to do this.

  24. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-arrayArea: `[T; N]`C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCT-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions