-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
[ER] get_two_mut #78541
Comments
You can write this without needing to go through the standard library: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=98fbe51263eeccb354ee3abef6e3e8eb |
Yeah, I can also write this, but I think it's worth adding to stdlib: fn get_two_mut<T, const N: usize>(this: &mut [T; N], i: usize, j: usize) -> (&mut T, &mut T) {
assert!(i < N && j < N && i != j);
let ptr = this.as_mut_ptr();
unsafe { (transmute(ptr.add(i)), transmute(ptr.add(j))) }
} (Here I've written it for an array, but for a slice it's very similar). |
I'm not sure your example is sound ... certainly not according to stacked borrows. You're taking a pointer with provenance for the whole array and making two |
Miri says it's fine and so would my intuition. Raw pointers, unlike references, do not assert their tags upon using their value but only when actually dereferenced and only for the place they are dereferenced to. But that only occurs as part of producing the
after the construction of at least one of the return values then it rightfully complains. That said, this is unecessary¹. This method can be written with entirely safe Rust. (I faintly remember already seeing this question/writing it out somewhere. It might be interesting to document the pattern somewhere). fn get_two_mut<T, const N: usize>(this: &mut [T; N], i: usize, j: usize) -> (&mut T, &mut T) {
let (i, j) = if i < j { (i,j) } else { (j,i) };
assert!(i < j && j < N);
let s0 = i;
// The position of the second split relative to the first.
let s1 = j - i;
let (_, split) = this.split_at_mut(s0);
let (first, second) = split.split_at_mut(s1);
(&mut first[0], &mut second[0])
} ¹Sadly llvm optimization is a lot worse than anticipated and this does no less than three panic bounds checks. For T = u32 and N = 64
|
The asm for my version (on slices): use std::mem::transmute;
type T = u32;
pub fn get_two_mut(this: &mut [T], i: usize, j: usize) -> (&mut T, &mut T) {
assert!(i < this.len() && j < this.len() && i != j);
let ptr = this.as_mut_ptr();
unsafe { (transmute(ptr.add(i)), transmute(ptr.add(j))) }
} Asm (optimized build): get_two_mut:
cmp rdx, rcx
je .LBB7_4
cmp rdx, rsi
jae .LBB7_4
cmp rcx, rsi
jae .LBB7_4
lea rax, [rdi + 4*rdx]
lea rdx, [rdi + 4*rcx]
ret
.LBB7_4:
push rax
call std::panicking::begin_panic
ud2 |
@HeroicKatora your implementation is wrong for inputs where This is my take: pub fn get_two_mut_safe(this: &mut [T], i: usize, j: usize) -> (&mut T, &mut T) {
assert!(i < this.len() && j < this.len() && i != j);
if i > j {
let (first, second) = this.split_at_mut(i);
(&mut second[0], &mut first[j])
} else {
let (first, second) = this.split_at_mut(j);
(&mut first[i], &mut second[0])
}
} This safe implementation is compiled to the same assembly as @leonardo-m's unsafe version. https://rust.godbolt.org/z/36c4sx Curiously swapping the two if branches (checking for |
@SkiFire13 Could you explain how? Although it's not incredibly important because your implementation looks incredibly pleasing and I'm going to use it going forward :) Note the normalization in the first line that ensures that always |
I would expect this function to always return the equivalent of |
Out of curiosity, what is "ER" for? |
Enhancement Request. |
I think the new get_many_mut method is enough, I think there isn't a need for get_two_mut now. |
In various cases I'd like a slice method similar to slice::split_at_mut but simpler, that returns just two items at different indexes:
That panics if i == j or i >= self.len() or i >= self.len().
The text was updated successfully, but these errors were encountered: