Auto merge of #151891 - jhpratt:rollup-cG0AjgK, r=jhpratt

Rollup of 5 pull requests

Successful merges:

 - rust-lang/rust#143650 (core: add Option::get_or_try_insert_with)
 - rust-lang/rust#151726 (Remove duplicated code in `slice/index.rs`)
 - rust-lang/rust#151812 (Add `shift_{left,right}` on slices)
 - rust-lang/rust#151829 (Implement `BinaryHeap::pop_if()`)
 - rust-lang/rust#151838 (Fix typo for Maybe dangling docs)
This commit is contained in:
bors 2026-01-31 06:37:16 +00:00
commit 1e9be1b77f
10 changed files with 344 additions and 41 deletions

View file

@ -649,6 +649,33 @@ impl<T: Ord, A: Allocator> BinaryHeap<T, A> {
})
}
/// Removes and returns the greatest item from the binary heap if the predicate
/// returns `true`, or [`None`] if the predicate returns false or the heap
/// is empty (the predicate will not be called in that case).
///
/// # Examples
///
/// ```
/// #![feature(binary_heap_pop_if)]
/// use std::collections::BinaryHeap;
/// let mut heap = BinaryHeap::from([1, 2]);
/// let pred = |x: &i32| *x % 2 == 0;
///
/// assert_eq!(heap.pop_if(pred), Some(2));
/// assert_eq!(heap.as_slice(), [1]);
/// assert_eq!(heap.pop_if(pred), None);
/// assert_eq!(heap.as_slice(), [1]);
/// ```
///
/// # Time complexity
///
/// The worst case cost of `pop_if` on a heap containing *n* elements is *O*(log(*n*)).
#[unstable(feature = "binary_heap_pop_if", issue = "151828")]
pub fn pop_if(&mut self, predicate: impl FnOnce(&T) -> bool) -> Option<T> {
let first = self.peek()?;
if predicate(first) { self.pop() } else { None }
}
/// Pushes an item onto the binary heap.
///
/// # Examples

View file

@ -136,6 +136,18 @@ fn test_peek_and_pop() {
}
}
#[test]
fn test_pop_if() {
let data = vec![9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
let mut sorted = data.clone();
sorted.sort();
let mut heap = BinaryHeap::from(data);
while let Some(popped) = heap.pop_if(|x| *x > 2) {
assert_eq!(popped, sorted.pop().unwrap());
}
assert_eq!(heap.into_sorted_vec(), vec![0, 1, 2]);
}
#[test]
fn test_peek_mut() {
let data = vec![2, 4, 6, 2, 1, 8, 10, 3, 5, 7, 0, 9, 1];

View file

@ -1,4 +1,5 @@
#![feature(allocator_api)]
#![feature(binary_heap_pop_if)]
#![feature(const_heap)]
#![feature(deque_extend_front)]
#![feature(iter_array_chunks)]

View file

@ -29,7 +29,7 @@ use crate::{mem, ptr};
/// mem::forget(boxed); // <-- this is UB!
/// ```
///
/// Even though the `Box`e's destructor is not run (and thus we don't have a double free bug), this
/// Even though the `Box`'s destructor is not run (and thus we don't have a double free bug), this
/// code is still UB. This is because when moving `boxed` into `forget`, its validity invariants
/// are asserted, causing UB since the `Box` is dangling. The safety comment is as such wrong, as
/// moving the `boxed` variable as part of the `forget` call *is* a use.

View file

@ -773,7 +773,7 @@ impl<T: Copy> Bound<&T> {
/// ```
#[unstable(feature = "bound_copied", issue = "145966")]
#[must_use]
pub fn copied(self) -> Bound<T> {
pub const fn copied(self) -> Bound<T> {
match self {
Bound::Unbounded => Bound::Unbounded,
Bound::Included(x) => Bound::Included(*x),

View file

@ -584,7 +584,7 @@
use crate::clone::TrivialClone;
use crate::iter::{self, FusedIterator, TrustedLen};
use crate::marker::Destruct;
use crate::ops::{self, ControlFlow, Deref, DerefMut};
use crate::ops::{self, ControlFlow, Deref, DerefMut, Residual, Try};
use crate::panicking::{panic, panic_display};
use crate::pin::Pin;
use crate::{cmp, convert, hint, mem, slice};
@ -1816,6 +1816,49 @@ impl<T> Option<T> {
unsafe { self.as_mut().unwrap_unchecked() }
}
/// If the option is `None`, calls the closure and inserts its output if successful.
///
/// If the closure returns a residual value such as `Err` or `None`,
/// that residual value is returned and nothing is inserted.
///
/// If the option is `Some`, nothing is inserted.
///
/// Unless a residual is returned, a mutable reference to the value
/// of the option will be output.
///
/// # Examples
///
/// ```
/// #![feature(option_get_or_try_insert_with)]
/// let mut o1: Option<u32> = None;
/// let mut o2: Option<u8> = None;
///
/// let number = "12345";
///
/// assert_eq!(o1.get_or_try_insert_with(|| number.parse()).copied(), Ok(12345));
/// assert!(o2.get_or_try_insert_with(|| number.parse()).is_err());
/// assert_eq!(o1, Some(12345));
/// assert_eq!(o2, None);
/// ```
#[inline]
#[unstable(feature = "option_get_or_try_insert_with", issue = "143648")]
pub fn get_or_try_insert_with<'a, R, F>(
&'a mut self,
f: F,
) -> <R::Residual as Residual<&'a mut T>>::TryType
where
F: FnOnce() -> R,
R: Try<Output = T, Residual: Residual<&'a mut T>>,
{
if let None = self {
*self = Some(f()?);
}
// SAFETY: a `None` variant for `self` would have been replaced by a `Some`
// variant in the code above.
Try::from_output(unsafe { self.as_mut().unwrap_unchecked() })
}
/////////////////////////////////////////////////////////////////////////
// Misc
/////////////////////////////////////////////////////////////////////////

View file

@ -910,29 +910,7 @@ where
R: [const] ops::RangeBounds<usize> + [const] Destruct,
{
let len = bounds.end;
let end = match range.end_bound() {
ops::Bound::Included(&end) if end >= len => slice_index_fail(0, end, len),
// Cannot overflow because `end < len` implies `end < usize::MAX`.
ops::Bound::Included(&end) => end + 1,
ops::Bound::Excluded(&end) if end > len => slice_index_fail(0, end, len),
ops::Bound::Excluded(&end) => end,
ops::Bound::Unbounded => len,
};
let start = match range.start_bound() {
ops::Bound::Excluded(&start) if start >= end => slice_index_fail(start, end, len),
// Cannot overflow because `start < end` implies `start < usize::MAX`.
ops::Bound::Excluded(&start) => start + 1,
ops::Bound::Included(&start) if start > end => slice_index_fail(start, end, len),
ops::Bound::Included(&start) => start,
ops::Bound::Unbounded => 0,
};
ops::Range { start, end }
into_slice_range(len, (range.start_bound().copied(), range.end_bound().copied()))
}
/// Performs bounds checking of a range without panicking.
@ -972,20 +950,8 @@ where
R: ops::RangeBounds<usize>,
{
let len = bounds.end;
let start = match range.start_bound() {
ops::Bound::Included(&start) => start,
ops::Bound::Excluded(start) => start.checked_add(1)?,
ops::Bound::Unbounded => 0,
};
let end = match range.end_bound() {
ops::Bound::Included(end) => end.checked_add(1)?,
ops::Bound::Excluded(&end) => end,
ops::Bound::Unbounded => len,
};
if start > end || end > len { None } else { Some(ops::Range { start, end }) }
let r = into_range(len, (range.start_bound().copied(), range.end_bound().copied()))?;
if r.start > r.end || r.end > len { None } else { Some(r) }
}
/// Converts a pair of `ops::Bound`s into `ops::Range` without performing any
@ -1011,6 +977,7 @@ pub(crate) const fn into_range_unchecked(
/// Converts pair of `ops::Bound`s into `ops::Range`.
/// Returns `None` on overflowing indices.
#[rustc_const_unstable(feature = "const_range", issue = "none")]
#[inline]
pub(crate) const fn into_range(
len: usize,
(start, end): (ops::Bound<usize>, ops::Bound<usize>),
@ -1036,7 +1003,8 @@ pub(crate) const fn into_range(
/// Converts pair of `ops::Bound`s into `ops::Range`.
/// Panics on overflowing indices.
pub(crate) fn into_slice_range(
#[inline]
pub(crate) const fn into_slice_range(
len: usize,
(start, end): (ops::Bound<usize>, ops::Bound<usize>),
) -> ops::Range<usize> {

View file

@ -3939,6 +3939,219 @@ impl<T> [T] {
}
}
/// Moves the elements of this slice `N` places to the left, returning the ones
/// that "fall off" the front, and putting `inserted` at the end.
///
/// Equivalently, you can think of concatenating `self` and `inserted` into one
/// long sequence, then returning the left-most `N` items and the rest into `self`:
///
/// ```text
/// self (before) inserted
/// vvvvvvvvvvvvvvv vvv
/// [1, 2, 3, 4, 5] [9]
/// ↙ ↙ ↙ ↙ ↙ ↙
/// [1] [2, 3, 4, 5, 9]
/// ^^^ ^^^^^^^^^^^^^^^
/// returned self (after)
/// ```
///
/// See also [`Self::shift_right`] and compare [`Self::rotate_left`].
///
/// # Examples
///
/// ```
/// #![feature(slice_shift)]
///
/// // Same as the diagram above
/// let mut a = [1, 2, 3, 4, 5];
/// let inserted = [9];
/// let returned = a.shift_left(inserted);
/// assert_eq!(returned, [1]);
/// assert_eq!(a, [2, 3, 4, 5, 9]);
///
/// // You can shift multiple items at a time
/// let mut a = *b"Hello world";
/// assert_eq!(a.shift_left(*b" peace"), *b"Hello ");
/// assert_eq!(a, *b"world peace");
///
/// // The name comes from this operation's similarity to bitshifts
/// let mut a: u8 = 0b10010110;
/// a <<= 3;
/// assert_eq!(a, 0b10110000_u8);
/// let mut a: [_; 8] = [1, 0, 0, 1, 0, 1, 1, 0];
/// a.shift_left([0; 3]);
/// assert_eq!(a, [1, 0, 1, 1, 0, 0, 0, 0]);
///
/// // Remember you can sub-slice to affect less that the whole slice.
/// // For example, this is similar to `.remove(1)` + `.insert(4, 'Z')`
/// let mut a = ['a', 'b', 'c', 'd', 'e', 'f'];
/// assert_eq!(a[1..=4].shift_left(['Z']), ['b']);
/// assert_eq!(a, ['a', 'c', 'd', 'e', 'Z', 'f']);
///
/// // If the size matches it's equivalent to `mem::replace`
/// let mut a = [1, 2, 3];
/// assert_eq!(a.shift_left([7, 8, 9]), [1, 2, 3]);
/// assert_eq!(a, [7, 8, 9]);
///
/// // Some of the "inserted" elements end up returned if the slice is too short
/// let mut a = [];
/// assert_eq!(a.shift_left([1, 2, 3]), [1, 2, 3]);
/// let mut a = [9];
/// assert_eq!(a.shift_left([1, 2, 3]), [9, 1, 2]);
/// assert_eq!(a, [3]);
/// ```
#[unstable(feature = "slice_shift", issue = "151772")]
pub const fn shift_left<const N: usize>(&mut self, inserted: [T; N]) -> [T; N] {
if let Some(shift) = self.len().checked_sub(N) {
// SAFETY: Having just checked that the inserted/returned arrays are
// shorter than (or the same length as) the slice:
// 1. The read for the items to return is in-bounds
// 2. We can `memmove` the slice over to cover the items we're returning
// to ensure those aren't double-dropped
// 3. Then we write (in-bounds for the same reason as the read) the
// inserted items atop the items of the slice that we just duplicated
//
// And none of this can panic, so there's no risk of intermediate unwinds.
unsafe {
let ptr = self.as_mut_ptr();
let returned = ptr.cast_array::<N>().read();
ptr.copy_from(ptr.add(N), shift);
ptr.add(shift).cast_array::<N>().write(inserted);
returned
}
} else {
// SAFETY: Having checked that the slice is strictly shorter than the
// inserted/returned arrays, it means we'll be copying the whole slice
// into the returned array, but that's not enough on its own. We also
// need to copy some of the inserted array into the returned array,
// with the rest going into the slice. Because `&mut` is exclusive
// and we own both `inserted` and `returned`, they're all disjoint
// allocations from each other as we can use `nonoverlapping` copies.
//
// We avoid double-frees by `ManuallyDrop`ing the inserted items,
// since we always copy them to other locations that will drop them
// instead. Plus nothing in here can panic -- it's just memcpy three
// times -- so there's no intermediate unwinds to worry about.
unsafe {
let len = self.len();
let slice = self.as_mut_ptr();
let inserted = mem::ManuallyDrop::new(inserted);
let inserted = (&raw const inserted).cast::<T>();
let mut returned = MaybeUninit::<[T; N]>::uninit();
let ptr = returned.as_mut_ptr().cast::<T>();
ptr.copy_from_nonoverlapping(slice, len);
ptr.add(len).copy_from_nonoverlapping(inserted, N - len);
slice.copy_from_nonoverlapping(inserted.add(N - len), len);
returned.assume_init()
}
}
}
/// Moves the elements of this slice `N` places to the right, returning the ones
/// that "fall off" the back, and putting `inserted` at the beginning.
///
/// Equivalently, you can think of concatenating `inserted` and `self` into one
/// long sequence, then returning the right-most `N` items and the rest into `self`:
///
/// ```text
/// inserted self (before)
/// vvv vvvvvvvvvvvvvvv
/// [0] [5, 6, 7, 8, 9]
/// ↘ ↘ ↘ ↘ ↘ ↘
/// [0, 5, 6, 7, 8] [9]
/// ^^^^^^^^^^^^^^^ ^^^
/// self (after) returned
/// ```
///
/// See also [`Self::shift_left`] and compare [`Self::rotate_right`].
///
/// # Examples
///
/// ```
/// #![feature(slice_shift)]
///
/// // Same as the diagram above
/// let mut a = [5, 6, 7, 8, 9];
/// let inserted = [0];
/// let returned = a.shift_right(inserted);
/// assert_eq!(returned, [9]);
/// assert_eq!(a, [0, 5, 6, 7, 8]);
///
/// // The name comes from this operation's similarity to bitshifts
/// let mut a: u8 = 0b10010110;
/// a >>= 3;
/// assert_eq!(a, 0b00010010_u8);
/// let mut a: [_; 8] = [1, 0, 0, 1, 0, 1, 1, 0];
/// a.shift_right([0; 3]);
/// assert_eq!(a, [0, 0, 0, 1, 0, 0, 1, 0]);
///
/// // Remember you can sub-slice to affect less that the whole slice.
/// // For example, this is similar to `.remove(4)` + `.insert(1, 'Z')`
/// let mut a = ['a', 'b', 'c', 'd', 'e', 'f'];
/// assert_eq!(a[1..=4].shift_right(['Z']), ['e']);
/// assert_eq!(a, ['a', 'Z', 'b', 'c', 'd', 'f']);
///
/// // If the size matches it's equivalent to `mem::replace`
/// let mut a = [1, 2, 3];
/// assert_eq!(a.shift_right([7, 8, 9]), [1, 2, 3]);
/// assert_eq!(a, [7, 8, 9]);
///
/// // Some of the "inserted" elements end up returned if the slice is too short
/// let mut a = [];
/// assert_eq!(a.shift_right([1, 2, 3]), [1, 2, 3]);
/// let mut a = [9];
/// assert_eq!(a.shift_right([1, 2, 3]), [2, 3, 9]);
/// assert_eq!(a, [1]);
/// ```
#[unstable(feature = "slice_shift", issue = "151772")]
pub const fn shift_right<const N: usize>(&mut self, inserted: [T; N]) -> [T; N] {
if let Some(shift) = self.len().checked_sub(N) {
// SAFETY: Having just checked that the inserted/returned arrays are
// shorter than (or the same length as) the slice:
// 1. The read for the items to return is in-bounds
// 2. We can `memmove` the slice over to cover the items we're returning
// to ensure those aren't double-dropped
// 3. Then we write (in-bounds for the same reason as the read) the
// inserted items atop the items of the slice that we just duplicated
//
// And none of this can panic, so there's no risk of intermediate unwinds.
unsafe {
let ptr = self.as_mut_ptr();
let returned = ptr.add(shift).cast_array::<N>().read();
ptr.add(N).copy_from(ptr, shift);
ptr.cast_array::<N>().write(inserted);
returned
}
} else {
// SAFETY: Having checked that the slice is strictly shorter than the
// inserted/returned arrays, it means we'll be copying the whole slice
// into the returned array, but that's not enough on its own. We also
// need to copy some of the inserted array into the returned array,
// with the rest going into the slice. Because `&mut` is exclusive
// and we own both `inserted` and `returned`, they're all disjoint
// allocations from each other as we can use `nonoverlapping` copies.
//
// We avoid double-frees by `ManuallyDrop`ing the inserted items,
// since we always copy them to other locations that will drop them
// instead. Plus nothing in here can panic -- it's just memcpy three
// times -- so there's no intermediate unwinds to worry about.
unsafe {
let len = self.len();
let slice = self.as_mut_ptr();
let inserted = mem::ManuallyDrop::new(inserted);
let inserted = (&raw const inserted).cast::<T>();
let mut returned = MaybeUninit::<[T; N]>::uninit();
let ptr = returned.as_mut_ptr().cast::<T>();
ptr.add(N - len).copy_from_nonoverlapping(slice, len);
ptr.copy_from_nonoverlapping(inserted.add(len), N - len);
slice.copy_from_nonoverlapping(inserted, len);
returned.assume_init()
}
}
}
/// Fills `self` with elements by cloning `value`.
///
/// # Examples

View file

@ -102,6 +102,7 @@
#![feature(slice_index_methods)]
#![feature(slice_internals)]
#![feature(slice_partition_dedup)]
#![feature(slice_shift)]
#![feature(slice_split_once)]
#![feature(sliceindex_wrappers)]
#![feature(split_array)]

View file

@ -2507,3 +2507,41 @@ fn test_slice_from_raw_parts_in_const() {
assert_eq!(EMPTY_SLICE.as_ptr().addr(), 123456);
assert_eq!(EMPTY_SLICE.len(), 0);
}
#[test]
fn test_shift_left() {
#[track_caller]
fn case<const M: usize, const N: usize>(
mut a: [i32; M],
i: [i32; N],
j: [i32; N],
b: [i32; M],
) {
assert_eq!((a.shift_left(i), a), (j, b));
}
case([], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], []);
case([1], [2, 3, 4, 5], [1, 2, 3, 4], [5]);
case([1, 2], [3, 4, 5], [1, 2, 3], [4, 5]);
case([1, 2, 3], [4, 5], [1, 2], [3, 4, 5]);
case([1, 2, 3, 4], [5], [1], [2, 3, 4, 5]);
case([1, 2, 3, 4, 5], [], [], [1, 2, 3, 4, 5]);
}
#[test]
fn test_shift_right() {
#[track_caller]
fn case<const M: usize, const N: usize>(
i: [i32; N],
mut a: [i32; M],
b: [i32; M],
j: [i32; N],
) {
assert_eq!((a.shift_right(i), a), (j, b));
}
case([], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], []);
case([1], [2, 3, 4, 5], [1, 2, 3, 4], [5]);
case([1, 2], [3, 4, 5], [1, 2, 3], [4, 5]);
case([1, 2, 3], [4, 5], [1, 2], [3, 4, 5]);
case([1, 2, 3, 4], [5], [1], [2, 3, 4, 5]);
case([1, 2, 3, 4, 5], [], [], [1, 2, 3, 4, 5]);
}