Skip to content

Commit 1dd515f

Browse files
authored
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.
2 parents 0f7d817 + 3fe37b8 commit 1dd515f

File tree

5 files changed

+201
-0
lines changed

5 files changed

+201
-0
lines changed

library/core/src/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,6 @@ impl Error for crate::ffi::FromBytesWithNulError {
506506

507507
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
508508
impl Error for crate::ffi::FromBytesUntilNulError {}
509+
510+
#[unstable(feature = "get_many_mut", issue = "104642")]
511+
impl<const N: usize> Error for crate::slice::GetManyMutError<N> {}

library/core/src/slice/mod.rs

+136
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#![stable(feature = "rust1", since = "1.0.0")]
88

99
use crate::cmp::Ordering::{self, Greater, Less};
10+
use crate::fmt;
1011
use crate::intrinsics::{assert_unsafe_precondition, exact_div};
1112
use crate::marker::Copy;
1213
use crate::mem::{self, SizedTypeProperties};
@@ -4082,6 +4083,88 @@ impl<T> [T] {
40824083
*self = rem;
40834084
Some(last)
40844085
}
4086+
4087+
/// Returns mutable references to many indices at once, without doing any checks.
4088+
///
4089+
/// For a safe alternative see [`get_many_mut`].
4090+
///
4091+
/// # Safety
4092+
///
4093+
/// Calling this method with overlapping or out-of-bounds indices is *[undefined behavior]*
4094+
/// even if the resulting references are not used.
4095+
///
4096+
/// # Examples
4097+
///
4098+
/// ```
4099+
/// #![feature(get_many_mut)]
4100+
///
4101+
/// let x = &mut [1, 2, 4];
4102+
///
4103+
/// unsafe {
4104+
/// let [a, b] = x.get_many_unchecked_mut([0, 2]);
4105+
/// *a *= 10;
4106+
/// *b *= 100;
4107+
/// }
4108+
/// assert_eq!(x, &[10, 2, 400]);
4109+
/// ```
4110+
///
4111+
/// [`get_many_mut`]: slice::get_many_mut
4112+
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
4113+
#[unstable(feature = "get_many_mut", issue = "104642")]
4114+
#[inline]
4115+
pub unsafe fn get_many_unchecked_mut<const N: usize>(
4116+
&mut self,
4117+
indices: [usize; N],
4118+
) -> [&mut T; N] {
4119+
// NB: This implementation is written as it is because any variation of
4120+
// `indices.map(|i| self.get_unchecked_mut(i))` would make miri unhappy,
4121+
// or generate worse code otherwise. This is also why we need to go
4122+
// through a raw pointer here.
4123+
let slice: *mut [T] = self;
4124+
let mut arr: mem::MaybeUninit<[&mut T; N]> = mem::MaybeUninit::uninit();
4125+
let arr_ptr = arr.as_mut_ptr();
4126+
4127+
// SAFETY: We expect `indices` to contain disjunct values that are
4128+
// in bounds of `self`.
4129+
unsafe {
4130+
for i in 0..N {
4131+
let idx = *indices.get_unchecked(i);
4132+
*(*arr_ptr).get_unchecked_mut(i) = &mut *slice.get_unchecked_mut(idx);
4133+
}
4134+
arr.assume_init()
4135+
}
4136+
}
4137+
4138+
/// Returns mutable references to many indices at once.
4139+
///
4140+
/// Returns an error if any index is out-of-bounds, or if the same index was
4141+
/// passed more than once.
4142+
///
4143+
/// # Examples
4144+
///
4145+
/// ```
4146+
/// #![feature(get_many_mut)]
4147+
///
4148+
/// let v = &mut [1, 2, 3];
4149+
/// if let Ok([a, b]) = v.get_many_mut([0, 2]) {
4150+
/// *a = 413;
4151+
/// *b = 612;
4152+
/// }
4153+
/// assert_eq!(v, &[413, 2, 612]);
4154+
/// ```
4155+
#[unstable(feature = "get_many_mut", issue = "104642")]
4156+
#[inline]
4157+
pub fn get_many_mut<const N: usize>(
4158+
&mut self,
4159+
indices: [usize; N],
4160+
) -> Result<[&mut T; N], GetManyMutError<N>> {
4161+
if !get_many_check_valid(&indices, self.len()) {
4162+
return Err(GetManyMutError { _private: () });
4163+
}
4164+
// SAFETY: The `get_many_check_valid()` call checked that all indices
4165+
// are disjunct and in bounds.
4166+
unsafe { Ok(self.get_many_unchecked_mut(indices)) }
4167+
}
40854168
}
40864169

40874170
impl<T, const N: usize> [[T; N]] {
@@ -4304,3 +4387,56 @@ impl<T, const N: usize> SlicePattern for [T; N] {
43044387
self
43054388
}
43064389
}
4390+
4391+
/// This checks every index against each other, and against `len`.
4392+
///
4393+
/// This will do `binomial(N + 1, 2) = N * (N + 1) / 2 = 0, 1, 3, 6, 10, ..`
4394+
/// comparison operations.
4395+
fn get_many_check_valid<const N: usize>(indices: &[usize; N], len: usize) -> bool {
4396+
// NB: The optimzer should inline the loops into a sequence
4397+
// of instructions without additional branching.
4398+
let mut valid = true;
4399+
for (i, &idx) in indices.iter().enumerate() {
4400+
valid &= idx < len;
4401+
for &idx2 in &indices[..i] {
4402+
valid &= idx != idx2;
4403+
}
4404+
}
4405+
valid
4406+
}
4407+
4408+
/// The error type returned by [`get_many_mut<N>`][`slice::get_many_mut`].
4409+
///
4410+
/// It indicates one of two possible errors:
4411+
/// - An index is out-of-bounds.
4412+
/// - The same index appeared multiple times in the array.
4413+
///
4414+
/// # Examples
4415+
///
4416+
/// ```
4417+
/// #![feature(get_many_mut)]
4418+
///
4419+
/// let v = &mut [1, 2, 3];
4420+
/// assert!(v.get_many_mut([0, 999]).is_err());
4421+
/// assert!(v.get_many_mut([1, 1]).is_err());
4422+
/// ```
4423+
#[unstable(feature = "get_many_mut", issue = "104642")]
4424+
// NB: The N here is there to be forward-compatible with adding more details
4425+
// to the error type at a later point
4426+
pub struct GetManyMutError<const N: usize> {
4427+
_private: (),
4428+
}
4429+
4430+
#[unstable(feature = "get_many_mut", issue = "104642")]
4431+
impl<const N: usize> fmt::Debug for GetManyMutError<N> {
4432+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4433+
f.debug_struct("GetManyMutError").finish_non_exhaustive()
4434+
}
4435+
}
4436+
4437+
#[unstable(feature = "get_many_mut", issue = "104642")]
4438+
impl<const N: usize> fmt::Display for GetManyMutError<N> {
4439+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4440+
fmt::Display::fmt("an index is out of bounds or appeared multiple times in the array", f)
4441+
}
4442+
}

library/core/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
#![feature(provide_any)]
109109
#![feature(utf8_chunks)]
110110
#![feature(is_ascii_octdigit)]
111+
#![feature(get_many_mut)]
111112
#![deny(unsafe_op_in_unsafe_fn)]
112113
#![deny(fuzzy_provenance_casts)]
113114

library/core/tests/slice.rs

+60
Original file line numberDiff line numberDiff line change
@@ -2595,3 +2595,63 @@ fn test_flatten_mut_size_overflow() {
25952595
let x = &mut [[(); usize::MAX]; 2][..];
25962596
let _ = x.flatten_mut();
25972597
}
2598+
2599+
#[test]
2600+
fn test_get_many_mut_normal_2() {
2601+
let mut v = vec![1, 2, 3, 4, 5];
2602+
let [a, b] = v.get_many_mut([3, 0]).unwrap();
2603+
*a += 10;
2604+
*b += 100;
2605+
assert_eq!(v, vec![101, 2, 3, 14, 5]);
2606+
}
2607+
2608+
#[test]
2609+
fn test_get_many_mut_normal_3() {
2610+
let mut v = vec![1, 2, 3, 4, 5];
2611+
let [a, b, c] = v.get_many_mut([0, 4, 2]).unwrap();
2612+
*a += 10;
2613+
*b += 100;
2614+
*c += 1000;
2615+
assert_eq!(v, vec![11, 2, 1003, 4, 105]);
2616+
}
2617+
2618+
#[test]
2619+
fn test_get_many_mut_empty() {
2620+
let mut v = vec![1, 2, 3, 4, 5];
2621+
let [] = v.get_many_mut([]).unwrap();
2622+
assert_eq!(v, vec![1, 2, 3, 4, 5]);
2623+
}
2624+
2625+
#[test]
2626+
fn test_get_many_mut_single_first() {
2627+
let mut v = vec![1, 2, 3, 4, 5];
2628+
let [a] = v.get_many_mut([0]).unwrap();
2629+
*a += 10;
2630+
assert_eq!(v, vec![11, 2, 3, 4, 5]);
2631+
}
2632+
2633+
#[test]
2634+
fn test_get_many_mut_single_last() {
2635+
let mut v = vec![1, 2, 3, 4, 5];
2636+
let [a] = v.get_many_mut([4]).unwrap();
2637+
*a += 10;
2638+
assert_eq!(v, vec![1, 2, 3, 4, 15]);
2639+
}
2640+
2641+
#[test]
2642+
fn test_get_many_mut_oob_nonempty() {
2643+
let mut v = vec![1, 2, 3, 4, 5];
2644+
assert!(v.get_many_mut([5]).is_err());
2645+
}
2646+
2647+
#[test]
2648+
fn test_get_many_mut_oob_empty() {
2649+
let mut v: Vec<i32> = vec![];
2650+
assert!(v.get_many_mut([0]).is_err());
2651+
}
2652+
2653+
#[test]
2654+
fn test_get_many_mut_duplicate() {
2655+
let mut v = vec![1, 2, 3, 4, 5];
2656+
assert!(v.get_many_mut([1, 3, 3, 4]).is_err());
2657+
}

library/std/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@
347347
#![feature(stdsimd)]
348348
#![feature(test)]
349349
#![feature(trace_macros)]
350+
#![feature(get_many_mut)]
350351
//
351352
// Only used in tests/benchmarks:
352353
//

0 commit comments

Comments
 (0)