Why did slice::swap violate the aliasing rule?

A discussion in IRLO brought #80682 slice::swap violates the aliasing rules to my attention. The code in question is:

pub fn swap(&mut self, a: usize, b: usize) {
    let pa: *mut T = &mut self[a];
    let pb: *mut T = &mut self[b];
    unsafe { 
        ptr::swap(pa, pb); 
    }
}

Apparently if a == b, this is actually UB due to creating multiple co-existing exclusive references. Perhaps a more to-the-point code sample would be (I hope this faithfully represents the original issue and still contains the UB; if not then I'm double confused):

pub fn demo() {
    let mut x = [123u32, 456u32];
    let a: *mut u32 = &mut x[0];
    let b: *mut u32 = &mut x[0];
    unsafe { std::ptr::swap(a, b); }
}

Why is this UB? And does removing the call to std::ptr::swap fix the UB. This is one of those things where from the author's perspective, it's clear they're not trying to create two mutable references that co-exist. But from Rust's perspective, apparently they are. This seems like a massive footgun because it's unintuitive, and I'd like to understand it.

I didn't post #80682 after a discussion on IRLO. Instead, I noticed the aliasing violating after reading the method's source, which someone had linked on HN. This is what made me open the issue.

The guarantee enforced by mutable references is:

Uniqueness guarantee. A mutable reference guarantees that between any two uses of the same mutable reference, no other pointer or reference has accessed the value. It also guarantees that if the mutable reference was used between any two uses of any other pointer/reference to the same value, then the mutable reference was created from that other pointer/reference (possibly recursively).

link

Here, the violated part here is the latter half. Namely, pa was used both before and after the creation of pb, but the creation of pb involves a mutable reference. Since pb was not created from pa, this is UB.

Note: creation of a mutable reference counts as a use

Another way to understand the rule that was violated is:

The way this should be interpreted when raw pointers are involved is that whenever you create or use a mutable reference to some value, all raw pointers to that value are invalidated.

(from same document)

4 Likes

Sorry, looks like I forgot to finish the sentence after pasting the GitHub URL. I meant to say this thread brought it to my attention.

Anyway, thanks for the answer; that addresses my quesiton.