Rollup merge of #83608 - Kimundi:index_many, r=Mark-Simulacrum
Add slice methods for indexing via an array of indices. Disclaimer: It's been a while since I contributed to the main Rust repo, apologies in advance if this is large enough already that it should've been an RFC. --- # Update: - Based on feedback, removed the `&[T]` variant of this API, and removed the requirements for the indices to be sorted. # Description This adds the following slice methods to `core`: ```rust impl<T> [T] { pub unsafe fn get_many_unchecked_mut<const N: usize>(&mut self, indices: [usize; N]) -> [&mut T; N]; pub fn get_many_mut<const N: usize>(&mut self, indices: [usize; N]) -> Option<[&mut T; N]>; } ``` This allows creating multiple mutable references to disjunct positions in a slice, which previously required writing some awkward code with `split_at_mut()` or `iter_mut()`. For the bound-checked variant, the indices are checked against each other and against the bounds of the slice, which requires `N * (N + 1) / 2` comparison operations. This has a proof-of-concept standalone implementation here: https://crates.io/crates/index_many Care has been taken that the implementation passes miri borrow checks, and generates straight-forward assembly (though this was only checked on x86_64). # Example ```rust let v = &mut [1, 2, 3, 4]; let [a, b] = v.get_many_mut([0, 2]).unwrap(); std::mem::swap(a, b); *v += 100; assert_eq!(v, &[3, 2, 101, 4]); ``` # Codegen Examples <details> <summary>Click to expand!</summary> Disclaimer: Taken from local tests with the standalone implementation. ## Unchecked Indexing: ```rust pub unsafe fn example_unchecked(slice: &mut [usize], indices: [usize; 3]) -> [&mut usize; 3] { slice.get_many_unchecked_mut(indices) } ``` ```nasm example_unchecked: mov rcx, qword, ptr, [r9] mov r8, qword, ptr, [r9, +, 8] mov r9, qword, ptr, [r9, +, 16] lea rcx, [rdx, +, 8*rcx] lea r8, [rdx, +, 8*r8] lea rdx, [rdx, +, 8*r9] mov qword, ptr, [rax], rcx mov qword, ptr, [rax, +, 8], r8 mov qword, ptr, [rax, +, 16], rdx ret ``` ## Checked Indexing (Option): ```rust pub unsafe fn example_option(slice: &mut [usize], indices: [usize; 3]) -> Option<[&mut usize; 3]> { slice.get_many_mut(indices) } ``` ```nasm mov r10, qword, ptr, [r9, +, 8] mov rcx, qword, ptr, [r9, +, 16] cmp rcx, r10 je .LBB0_7 mov r9, qword, ptr, [r9] cmp rcx, r9 je .LBB0_7 cmp rcx, r8 jae .LBB0_7 cmp r10, r9 je .LBB0_7 cmp r9, r8 jae .LBB0_7 cmp r10, r8 jae .LBB0_7 lea r8, [rdx, +, 8*r9] lea r9, [rdx, +, 8*r10] lea rcx, [rdx, +, 8*rcx] mov qword, ptr, [rax], r8 mov qword, ptr, [rax, +, 8], r9 mov qword, ptr, [rax, +, 16], rcx ret .LBB0_7: mov qword, ptr, [rax], 0 ret ``` ## Checked Indexing (Panic): ```rust pub fn example_panic(slice: &mut [usize], indices: [usize; 3]) -> [&mut usize; 3] { let len = slice.len(); match slice.get_many_mut(indices) { Some(s) => s, None => { let tmp = indices; index_many::sorted_bound_check_failed(&tmp, len) } } } ``` ```nasm example_panic: sub rsp, 56 mov rax, qword, ptr, [r9] mov r10, qword, ptr, [r9, +, 8] mov r9, qword, ptr, [r9, +, 16] cmp r9, r10 je .LBB0_6 cmp r9, rax je .LBB0_6 cmp r9, r8 jae .LBB0_6 cmp r10, rax je .LBB0_6 cmp rax, r8 jae .LBB0_6 cmp r10, r8 jae .LBB0_6 lea rax, [rdx, +, 8*rax] lea r8, [rdx, +, 8*r10] lea rdx, [rdx, +, 8*r9] mov qword, ptr, [rcx], rax mov qword, ptr, [rcx, +, 8], r8 mov qword, ptr, [rcx, +, 16], rdx mov rax, rcx add rsp, 56 ret .LBB0_6: mov qword, ptr, [rsp, +, 32], rax mov qword, ptr, [rsp, +, 40], r10 mov qword, ptr, [rsp, +, 48], r9 lea rcx, [rsp, +, 32] mov edx, 3 call index_many::bound_check_failed ud2 ``` </details> # Extensions There are multiple optional extensions to this. ## Indexing With Ranges This could easily be expanded to allow indexing with `[I; N]` where `I: SliceIndex<Self>`. I wanted to keep the initial implementation simple, so I didn't include it yet. ## Panicking Variant We could also add this method: ```rust impl<T> [T] { fn index_many_mut<const N: usize>(&mut self, indices: [usize; N]) -> [&mut T; N]; } ``` This would work similar to the regular index operator and panic with out-of-bound indices. The advantage would be that we could more easily ensure good codegen with a useful panic message, which is non-trivial with the `Option` variant. This is implemented in the standalone implementation, and used as basis for the codegen examples here and there.
This commit is contained in:
commit
1dd515f273
@ -506,3 +506,6 @@ fn description(&self) -> &str {
|
|||||||
|
|
||||||
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
|
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
|
||||||
impl Error for crate::ffi::FromBytesUntilNulError {}
|
impl Error for crate::ffi::FromBytesUntilNulError {}
|
||||||
|
|
||||||
|
#[unstable(feature = "get_many_mut", issue = "104642")]
|
||||||
|
impl<const N: usize> Error for crate::slice::GetManyMutError<N> {}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#![stable(feature = "rust1", since = "1.0.0")]
|
#![stable(feature = "rust1", since = "1.0.0")]
|
||||||
|
|
||||||
use crate::cmp::Ordering::{self, Greater, Less};
|
use crate::cmp::Ordering::{self, Greater, Less};
|
||||||
|
use crate::fmt;
|
||||||
use crate::intrinsics::{assert_unsafe_precondition, exact_div};
|
use crate::intrinsics::{assert_unsafe_precondition, exact_div};
|
||||||
use crate::marker::Copy;
|
use crate::marker::Copy;
|
||||||
use crate::mem::{self, SizedTypeProperties};
|
use crate::mem::{self, SizedTypeProperties};
|
||||||
@ -4082,6 +4083,88 @@ pub fn take_last_mut<'a>(self: &mut &'a mut Self) -> Option<&'a mut T> {
|
|||||||
*self = rem;
|
*self = rem;
|
||||||
Some(last)
|
Some(last)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns mutable references to many indices at once, without doing any checks.
|
||||||
|
///
|
||||||
|
/// For a safe alternative see [`get_many_mut`].
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Calling this method with overlapping or out-of-bounds indices is *[undefined behavior]*
|
||||||
|
/// even if the resulting references are not used.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(get_many_mut)]
|
||||||
|
///
|
||||||
|
/// let x = &mut [1, 2, 4];
|
||||||
|
///
|
||||||
|
/// unsafe {
|
||||||
|
/// let [a, b] = x.get_many_unchecked_mut([0, 2]);
|
||||||
|
/// *a *= 10;
|
||||||
|
/// *b *= 100;
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(x, &[10, 2, 400]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`get_many_mut`]: slice::get_many_mut
|
||||||
|
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
|
||||||
|
#[unstable(feature = "get_many_mut", issue = "104642")]
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn get_many_unchecked_mut<const N: usize>(
|
||||||
|
&mut self,
|
||||||
|
indices: [usize; N],
|
||||||
|
) -> [&mut T; N] {
|
||||||
|
// NB: This implementation is written as it is because any variation of
|
||||||
|
// `indices.map(|i| self.get_unchecked_mut(i))` would make miri unhappy,
|
||||||
|
// or generate worse code otherwise. This is also why we need to go
|
||||||
|
// through a raw pointer here.
|
||||||
|
let slice: *mut [T] = self;
|
||||||
|
let mut arr: mem::MaybeUninit<[&mut T; N]> = mem::MaybeUninit::uninit();
|
||||||
|
let arr_ptr = arr.as_mut_ptr();
|
||||||
|
|
||||||
|
// SAFETY: We expect `indices` to contain disjunct values that are
|
||||||
|
// in bounds of `self`.
|
||||||
|
unsafe {
|
||||||
|
for i in 0..N {
|
||||||
|
let idx = *indices.get_unchecked(i);
|
||||||
|
*(*arr_ptr).get_unchecked_mut(i) = &mut *slice.get_unchecked_mut(idx);
|
||||||
|
}
|
||||||
|
arr.assume_init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns mutable references to many indices at once.
|
||||||
|
///
|
||||||
|
/// Returns an error if any index is out-of-bounds, or if the same index was
|
||||||
|
/// passed more than once.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(get_many_mut)]
|
||||||
|
///
|
||||||
|
/// let v = &mut [1, 2, 3];
|
||||||
|
/// if let Ok([a, b]) = v.get_many_mut([0, 2]) {
|
||||||
|
/// *a = 413;
|
||||||
|
/// *b = 612;
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(v, &[413, 2, 612]);
|
||||||
|
/// ```
|
||||||
|
#[unstable(feature = "get_many_mut", issue = "104642")]
|
||||||
|
#[inline]
|
||||||
|
pub fn get_many_mut<const N: usize>(
|
||||||
|
&mut self,
|
||||||
|
indices: [usize; N],
|
||||||
|
) -> Result<[&mut T; N], GetManyMutError<N>> {
|
||||||
|
if !get_many_check_valid(&indices, self.len()) {
|
||||||
|
return Err(GetManyMutError { _private: () });
|
||||||
|
}
|
||||||
|
// SAFETY: The `get_many_check_valid()` call checked that all indices
|
||||||
|
// are disjunct and in bounds.
|
||||||
|
unsafe { Ok(self.get_many_unchecked_mut(indices)) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, const N: usize> [[T; N]] {
|
impl<T, const N: usize> [[T; N]] {
|
||||||
@ -4304,3 +4387,56 @@ fn as_slice(&self) -> &[Self::Item] {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This checks every index against each other, and against `len`.
|
||||||
|
///
|
||||||
|
/// This will do `binomial(N + 1, 2) = N * (N + 1) / 2 = 0, 1, 3, 6, 10, ..`
|
||||||
|
/// comparison operations.
|
||||||
|
fn get_many_check_valid<const N: usize>(indices: &[usize; N], len: usize) -> bool {
|
||||||
|
// NB: The optimzer should inline the loops into a sequence
|
||||||
|
// of instructions without additional branching.
|
||||||
|
let mut valid = true;
|
||||||
|
for (i, &idx) in indices.iter().enumerate() {
|
||||||
|
valid &= idx < len;
|
||||||
|
for &idx2 in &indices[..i] {
|
||||||
|
valid &= idx != idx2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valid
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error type returned by [`get_many_mut<N>`][`slice::get_many_mut`].
|
||||||
|
///
|
||||||
|
/// It indicates one of two possible errors:
|
||||||
|
/// - An index is out-of-bounds.
|
||||||
|
/// - The same index appeared multiple times in the array.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(get_many_mut)]
|
||||||
|
///
|
||||||
|
/// let v = &mut [1, 2, 3];
|
||||||
|
/// assert!(v.get_many_mut([0, 999]).is_err());
|
||||||
|
/// assert!(v.get_many_mut([1, 1]).is_err());
|
||||||
|
/// ```
|
||||||
|
#[unstable(feature = "get_many_mut", issue = "104642")]
|
||||||
|
// NB: The N here is there to be forward-compatible with adding more details
|
||||||
|
// to the error type at a later point
|
||||||
|
pub struct GetManyMutError<const N: usize> {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "get_many_mut", issue = "104642")]
|
||||||
|
impl<const N: usize> fmt::Debug for GetManyMutError<N> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("GetManyMutError").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "get_many_mut", issue = "104642")]
|
||||||
|
impl<const N: usize> fmt::Display for GetManyMutError<N> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt("an index is out of bounds or appeared multiple times in the array", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -108,6 +108,7 @@
|
|||||||
#![feature(provide_any)]
|
#![feature(provide_any)]
|
||||||
#![feature(utf8_chunks)]
|
#![feature(utf8_chunks)]
|
||||||
#![feature(is_ascii_octdigit)]
|
#![feature(is_ascii_octdigit)]
|
||||||
|
#![feature(get_many_mut)]
|
||||||
#![deny(unsafe_op_in_unsafe_fn)]
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
#![deny(fuzzy_provenance_casts)]
|
#![deny(fuzzy_provenance_casts)]
|
||||||
|
|
||||||
|
@ -2595,3 +2595,63 @@ fn test_flatten_mut_size_overflow() {
|
|||||||
let x = &mut [[(); usize::MAX]; 2][..];
|
let x = &mut [[(); usize::MAX]; 2][..];
|
||||||
let _ = x.flatten_mut();
|
let _ = x.flatten_mut();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_many_mut_normal_2() {
|
||||||
|
let mut v = vec![1, 2, 3, 4, 5];
|
||||||
|
let [a, b] = v.get_many_mut([3, 0]).unwrap();
|
||||||
|
*a += 10;
|
||||||
|
*b += 100;
|
||||||
|
assert_eq!(v, vec![101, 2, 3, 14, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_many_mut_normal_3() {
|
||||||
|
let mut v = vec![1, 2, 3, 4, 5];
|
||||||
|
let [a, b, c] = v.get_many_mut([0, 4, 2]).unwrap();
|
||||||
|
*a += 10;
|
||||||
|
*b += 100;
|
||||||
|
*c += 1000;
|
||||||
|
assert_eq!(v, vec![11, 2, 1003, 4, 105]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_many_mut_empty() {
|
||||||
|
let mut v = vec![1, 2, 3, 4, 5];
|
||||||
|
let [] = v.get_many_mut([]).unwrap();
|
||||||
|
assert_eq!(v, vec![1, 2, 3, 4, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_many_mut_single_first() {
|
||||||
|
let mut v = vec![1, 2, 3, 4, 5];
|
||||||
|
let [a] = v.get_many_mut([0]).unwrap();
|
||||||
|
*a += 10;
|
||||||
|
assert_eq!(v, vec![11, 2, 3, 4, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_many_mut_single_last() {
|
||||||
|
let mut v = vec![1, 2, 3, 4, 5];
|
||||||
|
let [a] = v.get_many_mut([4]).unwrap();
|
||||||
|
*a += 10;
|
||||||
|
assert_eq!(v, vec![1, 2, 3, 4, 15]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_many_mut_oob_nonempty() {
|
||||||
|
let mut v = vec![1, 2, 3, 4, 5];
|
||||||
|
assert!(v.get_many_mut([5]).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_many_mut_oob_empty() {
|
||||||
|
let mut v: Vec<i32> = vec![];
|
||||||
|
assert!(v.get_many_mut([0]).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_many_mut_duplicate() {
|
||||||
|
let mut v = vec![1, 2, 3, 4, 5];
|
||||||
|
assert!(v.get_many_mut([1, 3, 3, 4]).is_err());
|
||||||
|
}
|
||||||
|
@ -347,6 +347,7 @@
|
|||||||
#![feature(stdsimd)]
|
#![feature(stdsimd)]
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
#![feature(trace_macros)]
|
#![feature(trace_macros)]
|
||||||
|
#![feature(get_many_mut)]
|
||||||
//
|
//
|
||||||
// Only used in tests/benchmarks:
|
// Only used in tests/benchmarks:
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user