Rollup merge of #143554 - okaneco:const_slice_rotate, r=Amanieu,tgross35

slice: Mark `rotate_left`, `rotate_right` unstably const

Tracking issue rust-lang/rust#143812

- Add the const unstable `const_slice_rotate` feature
- Mark `<[T]>::rotate_left` and `<[T]>::rotate_right` as const unstable

The internal rotate functions use [`<*mut T>::replace`](https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.replace) and [`ptr::swap_nonoverlapping`](https://doc.rust-lang.org/stable/core/ptr/fn.swap_nonoverlapping.html) which were const-stabilized in 1.88.

Two changes were needed in the `rotate.rs` module to make these functions const:
1. A usage of `cmp::min` was replaced with a local function implementation of [`Ord::min`](https://doc.rust-lang.org/1.88.0/src/core/cmp.rs.html#1048-1053).
2. A `for start in 1..gcd` loop was changed to a while loop with an increment variable.

This needs libs-api approval and cc-ing const-eval.
This commit is contained in:
Matthias Krüger 2025-07-13 15:15:58 +02:00 committed by GitHub
commit 061bd28cee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 21 additions and 9 deletions

View file

@ -3700,7 +3700,8 @@ impl<T> [T] {
/// assert_eq!(a, ['a', 'c', 'd', 'e', 'b', 'f']);
/// ```
#[stable(feature = "slice_rotate", since = "1.26.0")]
pub fn rotate_left(&mut self, mid: usize) {
#[rustc_const_unstable(feature = "const_slice_rotate", issue = "143812")]
pub const fn rotate_left(&mut self, mid: usize) {
assert!(mid <= self.len());
let k = self.len() - mid;
let p = self.as_mut_ptr();
@ -3745,7 +3746,8 @@ impl<T> [T] {
/// assert_eq!(a, ['a', 'e', 'b', 'c', 'd', 'f']);
/// ```
#[stable(feature = "slice_rotate", since = "1.26.0")]
pub fn rotate_right(&mut self, k: usize) {
#[rustc_const_unstable(feature = "const_slice_rotate", issue = "143812")]
pub const fn rotate_right(&mut self, k: usize) {
assert!(k <= self.len());
let mid = self.len() - k;
let p = self.as_mut_ptr();

View file

@ -1,5 +1,5 @@
use crate::mem::{MaybeUninit, SizedTypeProperties};
use crate::{cmp, ptr};
use crate::ptr;
type BufType = [usize; 32];
@ -11,7 +11,7 @@ type BufType = [usize; 32];
///
/// The specified range must be valid for reading and writing.
#[inline]
pub(super) unsafe fn ptr_rotate<T>(left: usize, mid: *mut T, right: usize) {
pub(super) const unsafe fn ptr_rotate<T>(left: usize, mid: *mut T, right: usize) {
if T::IS_ZST {
return;
}
@ -21,7 +21,8 @@ pub(super) unsafe fn ptr_rotate<T>(left: usize, mid: *mut T, right: usize) {
}
// `T` is not a zero-sized type, so it's okay to divide by its size.
if !cfg!(feature = "optimize_for_size")
&& cmp::min(left, right) <= size_of::<BufType>() / size_of::<T>()
// FIXME(const-hack): Use cmp::min when available in const
&& const_min(left, right) <= size_of::<BufType>() / size_of::<T>()
{
// SAFETY: guaranteed by the caller
unsafe { ptr_rotate_memmove(left, mid, right) };
@ -45,7 +46,7 @@ pub(super) unsafe fn ptr_rotate<T>(left: usize, mid: *mut T, right: usize) {
///
/// The specified range must be valid for reading and writing.
#[inline]
unsafe fn ptr_rotate_memmove<T>(left: usize, mid: *mut T, right: usize) {
const unsafe fn ptr_rotate_memmove<T>(left: usize, mid: *mut T, right: usize) {
// The `[T; 0]` here is to ensure this is appropriately aligned for T
let mut rawarray = MaybeUninit::<(BufType, [T; 0])>::uninit();
let buf = rawarray.as_mut_ptr() as *mut T;
@ -117,7 +118,7 @@ unsafe fn ptr_rotate_memmove<T>(left: usize, mid: *mut T, right: usize) {
///
/// The specified range must be valid for reading and writing.
#[inline]
unsafe fn ptr_rotate_gcd<T>(left: usize, mid: *mut T, right: usize) {
const unsafe fn ptr_rotate_gcd<T>(left: usize, mid: *mut T, right: usize) {
// Algorithm 2
// Microbenchmarks indicate that the average performance for random shifts is better all
// the way until about `left + right == 32`, but the worst case performance breaks even
@ -175,7 +176,9 @@ unsafe fn ptr_rotate_gcd<T>(left: usize, mid: *mut T, right: usize) {
}
}
// finish the chunk with more rounds
for start in 1..gcd {
// FIXME(const-hack): Use `for start in 1..gcd` when available in const
let mut start = 1;
while start < gcd {
// SAFETY: `gcd` is at most equal to `right` so all values in `1..gcd` are valid for
// reading and writing as per the function's safety contract, see [long-safety-expl]
// above
@ -201,6 +204,8 @@ unsafe fn ptr_rotate_gcd<T>(left: usize, mid: *mut T, right: usize) {
i += right;
}
}
start += 1;
}
}
@ -222,7 +227,7 @@ unsafe fn ptr_rotate_gcd<T>(left: usize, mid: *mut T, right: usize) {
///
/// The specified range must be valid for reading and writing.
#[inline]
unsafe fn ptr_rotate_swap<T>(mut left: usize, mut mid: *mut T, mut right: usize) {
const unsafe fn ptr_rotate_swap<T>(mut left: usize, mut mid: *mut T, mut right: usize) {
loop {
if left >= right {
// Algorithm 3
@ -265,3 +270,8 @@ unsafe fn ptr_rotate_swap<T>(mut left: usize, mut mid: *mut T, mut right: usize)
}
}
}
// FIXME(const-hack): Use cmp::min when available in const
const fn const_min(left: usize, right: usize) -> usize {
if right < left { right } else { left }
}