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 take_... functions to slices #62282

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/libcore/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ pub use self::range::{Range, RangeFrom, RangeFull, RangeTo};
#[stable(feature = "inclusive_range", since = "1.26.0")]
pub use self::range::{Bound, RangeBounds, RangeInclusive, RangeToInclusive};

#[unstable(feature = "one_sided_range", issue = "69780")]
pub use self::range::OneSidedRange;

#[unstable(feature = "try_trait", issue = "42327")]
pub use self::r#try::Try;

Expand Down
18 changes: 18 additions & 0 deletions src/libcore/ops/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -891,3 +891,21 @@ impl<T> RangeBounds<T> for RangeToInclusive<&T> {
Included(self.end)
}
}

/// `OneSidedRange` is implemented by Rust's built-in range types which
/// are unbounded on one side. For example, `a..`, `..b` and `..=c` implement
/// `OneSidedRange`, but `..`, `d..e`, and `f..=g` do not.
///
/// Types which implement `OneSidedRange<T>` must return `Bound::Unbounded`
/// from exactly one of `RangeBounds::start_bound` and `RangeBounds::end_bound`.
#[unstable(feature = "one_sided_range", issue = "69780")]
pub trait OneSidedRange<T: ?Sized>: RangeBounds<T> {}

#[unstable(feature = "one_sided_range", issue = "69780")]
impl<T> OneSidedRange<T> for RangeTo<T> where Self: RangeBounds<T> {}

#[unstable(feature = "one_sided_range", issue = "69780")]
impl<T> OneSidedRange<T> for RangeFrom<T> where Self: RangeBounds<T> {}

#[unstable(feature = "one_sided_range", issue = "69780")]
impl<T> OneSidedRange<T> for RangeToInclusive<T> where Self: RangeBounds<T> {}
241 changes: 240 additions & 1 deletion src/libcore/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::isize;
use crate::iter::*;
use crate::marker::{self, Copy, Send, Sized, Sync};
use crate::mem;
use crate::ops::{self, FnMut, Range};
use crate::ops::{self, Bound, FnMut, OneSidedRange, Range};
use crate::option::Option;
use crate::option::Option::{None, Some};
use crate::ptr::{self, NonNull};
Expand All @@ -53,6 +53,24 @@ mod sort;
// Extension traits
//

/// Calculates the direction and split point of a one-sided range.
///
/// Helper for `take` and `take_mut` which returns a boolean
/// indicating whether the front of the split is being taken
/// (as opposed to the back), as well as a number indicating the
/// index at which to split. Returns `None` if the split index would
/// overflow `usize`.
#[inline]
fn take_split_point(range: impl OneSidedRange<usize>) -> Option<(bool, usize)> {
Some(match (range.start_bound(), range.end_bound()) {
(Bound::Unbounded, Bound::Excluded(i)) => (true, *i),
(Bound::Unbounded, Bound::Included(i)) => (true, i.checked_add(1)?),
(Bound::Excluded(i), Bound::Unbounded) => (false, i.checked_add(1)?),
(Bound::Included(i), Bound::Unbounded) => (false, *i),
_ => unreachable!(),
})
}

#[lang = "slice"]
#[cfg(not(test))]
impl<T> [T] {
Expand Down Expand Up @@ -2639,6 +2657,227 @@ impl<T> [T] {
{
self.iter().is_sorted_by_key(f)
}

/// Removes and returns the portion of the slice specified by `range`.
///
/// If the provided `range` starts or ends outside of the slice,
/// `None` is returned and the slice is not modified.
///
/// # Examples
///
/// Taking the first three items from a slice (via `..3`):
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &[_] = &['a', 'b', 'c', 'd'];
/// let mut first_three = slice.take(..3).unwrap();
///
/// assert_eq!(slice, &['d']);
/// assert_eq!(first_three, &['a', 'b', 'c']);
/// ```
///
/// Taking the tail of a slice starting at index two (via `2..`):
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &[_] = &['a', 'b', 'c', 'd'];
/// let mut tail = slice.take(2..).unwrap();
///
/// assert_eq!(slice, &['a', 'b']);
/// assert_eq!(tail, &['c', 'd']);
/// ```
cramertj marked this conversation as resolved.
Show resolved Hide resolved
///
/// Getting `None` when `range` starts or ends outside of the slice:
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &[_] = &['a', 'b', 'c', 'd'];
///
/// assert_eq!(None, slice.take(5..));
/// assert_eq!(None, slice.take(..5));
/// assert_eq!(None, slice.take(..=4));
/// let expected: &[char] = &['a', 'b', 'c', 'd'];
/// assert_eq!(Some(expected), slice.take(..4));
/// ```
#[inline]
#[unstable(feature = "slice_take", issue = "62280")]
pub fn take<'a, R: OneSidedRange<usize>>(self: &mut &'a Self, range: R) -> Option<&'a Self> {
let (taking_front, split_index) = take_split_point(range)?;
if split_index > self.len() {
return None;
}
let original = crate::mem::take(self);
let (front, back) = original.split_at(split_index);
if taking_front {
*self = back;
Some(front)
} else {
*self = front;
Some(back)
}
}

/// Removes and returns the portion of the mutable slice specified by `range`.
///
/// If the provided `range` starts or ends outside of the slice,
/// `None` is returned and the slice is not modified.
///
/// # Examples
///
/// Taking the first three items from a slice (via `..3`):
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &mut [_] = &mut ['a', 'b', 'c', 'd'];
/// let mut first_three = slice.take_mut(..3).unwrap();
///
/// assert_eq!(slice, &mut ['d']);
/// assert_eq!(first_three, &mut ['a', 'b', 'c']);
/// ```
///
/// Taking the tail of a slice starting at index two (via `2..`):
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &mut [_] = &mut ['a', 'b', 'c', 'd'];
/// let mut tail = slice.take_mut(2..).unwrap();
///
/// assert_eq!(slice, &mut ['a', 'b']);
/// assert_eq!(tail, &mut ['c', 'd']);
/// ```
cramertj marked this conversation as resolved.
Show resolved Hide resolved
///
/// Getting `None` when `range` starts or ends outside of the slice:
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &mut [_] = &mut ['a', 'b', 'c', 'd'];
///
/// assert_eq!(None, slice.take_mut(5..));
/// assert_eq!(None, slice.take_mut(..5));
/// assert_eq!(None, slice.take_mut(..=4));
/// let expected: &mut [_] = &mut ['a', 'b', 'c', 'd'];
/// assert_eq!(Some(expected), slice.take_mut(..4));
/// ```
#[inline]
#[unstable(feature = "slice_take", issue = "62280")]
pub fn take_mut<'a, R: OneSidedRange<usize>>(
self: &mut &'a mut Self,
range: R,
) -> Option<&'a mut Self> {
let (taking_front, split_index) = take_split_point(range)?;
if split_index > self.len() {
return None;
}
let original = crate::mem::take(self);
let (front, back) = original.split_at_mut(split_index);
if taking_front {
*self = back;
Some(front)
} else {
*self = front;
Some(back)
}
}

/// Takes the first element out of the slice.
///
/// Returns a reference pointing to the first element of the old slice.
Copy link
Contributor

Choose a reason for hiding this comment

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

There is no such thing as an old slice, given there is not really a new slice either.

///
/// Returns `None` if the slice is empty.
Comment on lines +2788 to +2792
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we want to adopt the formulation from the take and take_mut docs for this and the three other methods below? I.e.:

Removes and returns the [first|last] element from the slice.

If the slice is empty, None is returned and the slice is not modified.

This would also resolve @nox's remark.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm a little confused about how to phrase this, because I want to make it clear that the element itself is staying in place, and the only thing being manipulated is what the slice refers to. That is, "Removes and returns the first element from the slice." sounds to me like it's taking an element out from the backing memory, which isn't the case. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, you're right, it's a bit misleading. I don't have a good idea, but maybe something like:

Returns a reference to the first element and modifies self to now start at the second element.

or

Returns a reference to the first element and modifies self to not include that element anymore.

or

Returns a reference to the first element and sets self to &self[1..].

In all cases, the first "self" could be replaced by "this slice". And in all cases, I would add as a second paragraph:

If the slice is empty, None is returned and [self|the slice] is not modified. This function is essentially equivalent to self.take(1..), but returns &T instead of &[T].

And if we change the take_[first|last] versions of this method, we should probably also adjust the formulation for take[_mut]. It currently says "removes and returns" which, as you said, is misleading.

///
/// # Examples
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &[_] = &['a', 'b', 'c'];
/// let first = slice.take_first().unwrap();
///
/// assert_eq!(slice, &['b', 'c']);
/// assert_eq!(first, &'a');
/// ```
#[inline]
#[unstable(feature = "slice_take", issue = "62280")]
pub fn take_first<'a>(self: &mut &'a Self) -> Option<&'a T> {
self.take(..=0).map(|res| &res[0])
}

/// Takes the first element out of the mutable slice.
///
/// Returns a mutable reference pointing to the first element of the old slice.
///
/// Returns `None` if the slice is empty.
///
/// # Examples
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &mut [_] = &mut ['a', 'b', 'c'];
/// let first = slice.take_first_mut().unwrap();
/// *first = 'd';
///
/// assert_eq!(slice, &['b', 'c']);
/// assert_eq!(first, &'d');
/// ```
#[inline]
#[unstable(feature = "slice_take", issue = "62280")]
pub fn take_first_mut<'a>(self: &mut &'a mut Self) -> Option<&'a mut T> {
self.take_mut(..=0).map(|res| &mut res[0])
}

/// Takes the last element out of the slice.
///
/// Returns a reference pointing to the last element of the old slice.
///
/// Returns `None` if the slice is empty.
///
/// # Examples
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &[_] = &['a', 'b', 'c'];
/// let last = slice.take_last().unwrap();
///
/// assert_eq!(slice, &['a', 'b']);
/// assert_eq!(last, &'c');
/// ```
#[inline]
#[unstable(feature = "slice_take", issue = "62280")]
pub fn take_last<'a>(self: &mut &'a Self) -> Option<&'a T> {
self.take((self.len() - 1)..).map(|res| &res[0])
}

/// Takes the last element out of the mutable slice.
///
/// Returns a mutable reference pointing to the last element of the old slice.
///
/// Returns `None` if the slice is empty.
///
/// # Examples
///
/// ```
/// #![feature(slice_take)]
///
/// let mut slice: &mut [_] = &mut ['a', 'b', 'c'];
/// let last = slice.take_last_mut().unwrap();
/// *last = 'd';
///
/// assert_eq!(slice, &['a', 'b']);
/// assert_eq!(last, &'d');
/// ```
#[inline]
#[unstable(feature = "slice_take", issue = "62280")]
pub fn take_last_mut<'a>(self: &mut &'a mut Self) -> Option<&'a mut T> {
self.take_mut((self.len() - 1)..).map(|res| &mut res[0])
}
}

#[lang = "slice_u8"]
Expand Down
1 change: 1 addition & 0 deletions src/libcore/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#![feature(unwrap_infallible)]
#![feature(leading_trailing_ones)]
#![feature(const_forget)]
#![feature(slice_take)]

extern crate test;

Expand Down
Loading