Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add slice::{split_,}{first,last}_chunk{,_mut} #95198

Merged
merged 1 commit into from
May 25, 2023

Conversation

clarfonthey
Copy link
Contributor

@clarfonthey clarfonthey commented Mar 22, 2022

This adds to the existing tracking issue for slice::array_chunks (#74985) under a separate feature, slice_get_chunk.

Currently, we have the existing first/last API for slices:

impl [T] {
    pub const fn first(&self) -> Option<&T>;
    pub const fn first_mut(&mut self) -> Option<&mut T>;
    pub const fn last(&self) -> Option<&T>;
    pub const fn last_mut(&mut self) -> Option<&mut T>;
    pub const fn split_first(&self) -> Option<(&T, &[T])>;
    pub const fn split_first_mut(&mut self) -> Option<(&mut T, &mut [T])>;
    pub const fn split_last(&self) -> Option<(&T, &[T])>;
    pub const fn split_last_mut(&mut self) -> Option<(&mut T, &mut [T])>;
}

This augments it with a first_chunk/last_chunk API that allows retrieving multiple elements at once:

impl [T] {
    pub const fn first_chunk<const N: usize>(&self) -> Option<&[T; N]>;
    pub const fn first_chunk_mut<const N: usize>(&mut self) -> Option<&mut [T; N]>;
    pub const fn last_chunk<const N: usize>(&self) -> Option<&[T; N]>;
    pub const fn last_chunk_mut<const N: usize>(&mut self) -> Option<&mut [T; N]>;
    pub const fn split_first_chunk<const N: usize>(&self) -> Option<(&[T; N], &[T])>;
    pub const 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; N], &[T])>;
    pub const fn split_last_chunk_mut<const N: usize>(&mut self) -> Option<(&mut [T; N], &mut [T])>;
}

The code is based off of a copy of the existing API, with the documentation and examples properly modified. Currently, the most common way to perform these kinds of lookups with the existing methods is via slice.as_chunks::<N>().0[0] or the worse slice.as_chunks::<N>().0[slice.len() - N], which is substantially less readable than slice.first_chunk::<N>() or slice.last_chunk::<N>().

ACP: rust-lang/libs-team#69

@rust-highfive
Copy link
Collaborator

r? @yaahc

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 22, 2022
@rust-log-analyzer

This comment has been minimized.

library/core/src/slice/mod.rs Outdated Show resolved Hide resolved
@scottmcm
Copy link
Member

I've definitely written a bunch of examples that did .as_chunks().0[0], so 👍 to having something in this area.

I don't know if this is how I'd expect it to look, though. For example, the indexing could be left to subslicing, like a[i..].prefix_chunk() (instead of .get_chunk(i)) or a[..i].suffix_chunk() (instead of .get_chunk(i - N)).

Of course, the other thing that comes to mind is wanting split_at_*-style APIs for &[T] -> (&[T; N], &[T]) and &[T] -> (&[T]; &[T; N]). Or maybe chunk versions of the take_{first/last} APIs?

@clarfonthey
Copy link
Contributor Author

Hmm, you do have a point about subslicing; it might be more natural to have slice[idx..].first_chunk::<N>() and slice[..idx].last_chunk::<N>(). However, in this case, it would make sense to return Option<[T; N]> which offloads the bounds checking to the user.

Essentially, we'd mimic the first and last APIs but with chunky semantics, accepting const generics. I'm down to make these changes if that seems like the most natural way forward.

@scottmcm
Copy link
Member

I don't know what the best approach would be here. Might be worth seeing if libs-api has any specific preferences.

The whole "add const generic versions of most of the slice stuff" is a big project, but seems useful, so maybe they could have some generalized guidance of how they'd like to see it done? If that existed then I'd be happy to sign off on a bunch of individual PRs for adding the various pieces.

@clarfonthey
Copy link
Contributor Author

clarfonthey commented Mar 22, 2022

That's incredibly fair. Since I was feeling particularly motivated, I decided to go ahead and implement the first/last chunk semantics that I mentioned and maybe that can help explain how this would work out.

I personally find the result rather intuitive, although the chunk naming will probably need work before stabilisation. Hopefully seeing how this would work in code will help iron out that process.

EDIT: And I finished modifying the PR description to properly summarise the new API and the use cases it allows.

@clarfonthey clarfonthey changed the title Add slice::get_chunk{,_mut} Add slice::{split_,}{first,last}_chunk{,_mut} Mar 22, 2022
@@ -151,6 +151,7 @@
#![feature(const_slice_from_ref)]
#![feature(const_slice_index)]
#![feature(const_is_char_boundary)]
#![feature(slice_split_at_unchecked)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I genuinely have no idea how this wasn't required before, since the existing implementation of split_at requires split_at_unchecked ??

Anyway, I decided to reuse this code for these methods and needed this.

@scottmcm
Copy link
Member

These look great to me, but apparently I was blind looking at the rustdoc, as I forgot about https://doc.rust-lang.org/nightly/std/primitive.slice.html#method.split_array_mut from #90091 (comment) .

I personally like these better, because they solve the Option-ness question.

@scottmcm scottmcm mentioned this pull request Mar 22, 2022
2 tasks
@clarfonthey
Copy link
Contributor Author

Yeah, it's always a challenge to figure out what's there when there are so many methods and they're not really sorted in any logical way. But, I guess, that's a challenge for another day.

Before merge, we can maybe decide which tracking issue this should go in, since, in hindsight, it makes the most sense for it to have its own tracking issue.

@jethrogb
Copy link
Contributor

I don't think chunk sufficiently conveys these APIs deal with constant-size arrays, give the existing chunks API.

@yaahc yaahc added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Mar 23, 2022
@bors
Copy link
Contributor

bors commented Mar 26, 2022

☔ The latest upstream changes (presumably #95274) made this pull request unmergeable. Please resolve the merge conflicts.

@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 24, 2022
@Dylan-DPC
Copy link
Member

r? @rust-lang/libs-api

@rust-highfive rust-highfive assigned joshtriplett and unassigned yaahc Apr 25, 2022
@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 20, 2022
@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 24, 2022
@scottmcm
Copy link
Member

scottmcm commented Aug 20, 2022

Nominating for libs-api to try to unblock this. (Since it just came up on URLO.)

Should we just merge to nightly as fine for unstable? Do you have strong feelings about the direction to take for "get arrays from slices" APIs? Does this need an ACP/RFC now? (Of course ACPs didn't exist when this PR was opened.)

@bors
Copy link
Contributor

bors commented Aug 26, 2022

☔ The latest upstream changes (presumably #101017) made this pull request unmergeable. Please resolve the merge conflicts.

@GKFX
Copy link
Contributor

GKFX commented Nov 11, 2022

With the APIs proposed, I don't see a nice way to extract a subslice at arbitrary offset (and fixed length) as an array and get an Option without panicking. s[a..].first_chunk::<N>() will panic for s.len() < a, and s.get(a..).and_then(|x| x.first_chunk::<N>()) is verbose. Rather than having to add another API at a later date, I would replace first_chunk and first_chunk_mut with get_array[_mut], by analogy to plain slice::get[_mut]:

impl [T] {
    pub const fn get_array<const N: usize>(&self, start_idx: usize) -> Option<&[T; N]>;
    pub const fn get_array_mut<const N: usize>(&mut self, start_idx: usize) -> Option<&mut [T; N]>;
}

I think it's acceptable to have to specify a zero to get the first chunk: s.get_array(0) is no longer than s.first_chunk().

We discussed this in today's @rust-lang/libs-api meeting. We felt that the chunks and rchunks and chunks_mut and rchunks_mut methods (and the array_ versions) address the half of these that don't involve split.

We'd be interested to see if the iterator types returned by these methods could address the split variants of these by having a method on that iterator type. For instance, a remaining method, or a split method that returns the pair of next and the remaining items.

The methods array_chunks etc. do not permit zero-length arrays, so using array_chunks in place of first_chunk would have an unnecessary panic (or compile error) in that case. An edge case, but could be awkward.

@Dylan-DPC Dylan-DPC added S-waiting-on-ACP Status: PR has an ACP and is waiting for the ACP to complete. and removed S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work. labels Mar 6, 2023
@scottmcm scottmcm added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-ACP Status: PR has an ACP and is waiting for the ACP to complete. labels May 18, 2023
@scottmcm
Copy link
Member

scottmcm commented May 18, 2023

Looks like the ACP was accepted! 🎉

@clarfonthey Can you rebase and resolve conflicts and such please?

@clarfonthey clarfonthey force-pushed the get_chunk branch 2 times, most recently from 2baf530 to 1974c7f Compare May 19, 2023 22:12
@rust-log-analyzer

This comment has been minimized.

@clarfonthey
Copy link
Contributor Author

(Going to wait on final CI run to pass the torch, but things should be rebased now. The biggest change I had to make was using split_at_unchecked instead of get_unchecked now that all existing uses of const traits have been purged from libstd to make way for keyword generics. I don't think this should negatively affect performance.)

@clarfonthey
Copy link
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 21, 2023
#[unstable(feature = "slice_first_last_chunk", issue = "111774")]
#[rustc_const_unstable(feature = "slice_first_last_chunk", issue = "111774")]
#[inline]
pub const fn split_last_chunk<const N: usize>(&self) -> Option<(&[T; N], &[T])> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I would have expected this to be -> Option<(&[T], &[T; N])>, but this is consistent with split_last, so I guess it's fine for now. I'll put a note in the tracking issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, there are arguments for both but honestly, I don't feel too swayed either way. I think that the potential issues here are greater because of how the types are only nominally different and can both be coerced to be the same, although I don't think there's a whole much that can be done about that than encourage folks to pay more attention.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm worried that someone might try something that boils down to let (head, tail) = x.split_last_chunk(); frobble(tail[0]) and being confused when it compiles (as you said) but doesn't do what they expected.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that should probably be mentioned on the tracking issue, because I would write something like that... 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WaffleLapkin Is the note in #111774 (comment) not sufficient?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is sufficient, thanks

@scottmcm
Copy link
Member

Thanks! I'm excited to have these.

@bors r+ rollup

@bors
Copy link
Contributor

bors commented May 24, 2023

📌 Commit d58dd10 has been approved by scottmcm

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 24, 2023
bors added a commit to rust-lang-ci/rust that referenced this pull request May 25, 2023
…iaskrgr

Rollup of 4 pull requests

Successful merges:

 - rust-lang#95198 (Add slice::{split_,}{first,last}_chunk{,_mut})
 - rust-lang#109899 (Use apple-m1 as target CPU for aarch64-apple-darwin.)
 - rust-lang#111624 (Emit diagnostic for privately uninhabited uncovered witnesses.)
 - rust-lang#111875 (Don't leak the function that is called on drop)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 8497948 into rust-lang:master May 25, 2023
@rustbot rustbot added this to the 1.71.0 milestone May 25, 2023
@clarfonthey clarfonthey deleted the get_chunk branch September 16, 2023 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.