Kapichu
Kapichu

Reputation: 3706

How does `std::mem::swap` work?

Swap the values at two mutable locations of the same type, without deinitialising or copying either one.

use std::mem;

let x = &mut 5;
let y = &mut 42;

mem::swap(x, y);

assert_eq!(42, *x);
assert_eq!(5, *y);

(From offical Rust doc)

How can two values be swapped without copying? How did the value 42 go from y to x? This shouldn't be possible.

Upvotes: 16

Views: 6682

Answers (2)

Shepmaster
Shepmaster

Reputation: 430841

The previous answer is correct in semantics, but outdated in exact details.

Logically, swapping two values works by reading value A into a temporary location, copying B on top of A, then writing the temporary value back into B. There is a brief period where the same value exists twice in memory. This is why the implementation of these functions requires unsafe code, as only a human can guarantee that Rust's safety requirements are upheld.

As of Rust 1.43.0, mem::swap is implemented as:

pub fn swap<T>(x: &mut T, y: &mut T) {
    // SAFETY: the raw pointers have been created from safe mutable references satisfying all the
    // constraints on `ptr::swap_nonoverlapping_one`
    unsafe {
        ptr::swap_nonoverlapping_one(x, y);
    }
}

swap_nonoverlapping_one is private, but its implementation is:

pub(crate) unsafe fn swap_nonoverlapping_one<T>(x: *mut T, y: *mut T) {
    // For types smaller than the block optimization below,
    // just swap directly to avoid pessimizing codegen.
    if mem::size_of::<T>() < 32 {
        let z = read(x);
        copy_nonoverlapping(y, x, 1);
        write(y, z);
    } else {
        swap_nonoverlapping(x, y, 1);
    }
}

You can see the documentation for ptr::copy_nonoverlapping and ptr::swap_nonoverlapping. The latter is basically a highly-optimized version of copying for larger values.

Upvotes: 13

Levans
Levans

Reputation: 15002

The function does actually make a copy internally: here is its source extracted from the documentation:

pub fn swap<T>(x: &mut T, y: &mut T) {
    unsafe {
        // Give ourselves some scratch space to work with
        let mut t: T = uninitialized();

        // Perform the swap, `&mut` pointers never alias
        ptr::copy_nonoverlapping(&*x, &mut t, 1);
        ptr::copy_nonoverlapping(&*y, x, 1);
        ptr::copy_nonoverlapping(&t, y, 1);

        // y and t now point to the same thing,
        // but we need to completely forget `t`
        // because it's no longer relevant.
        forget(t);
    }
}

Upvotes: 13

Related Questions