Why does `copy` from `slice` cause UB but not from `slice.as_ptr()`?

I was trying to fill a slightly awkward FFI structure using ptr::copy and my app crashed. When debugging, I realized that copy(slice, ...) behaves differently than copy(slice.as_ptr(), ...), which I found surprising.

Here is the code in question:

#[derive(Debug)]
struct S {
    array: [u8; 256],
}

fn main() {
    let mut s = S { array: [0; 256] };
    let hello = b"hello world\0";

    unsafe {
        std::ptr::copy(hello         , s.array.as_mut_ptr() as *mut _, hello.len());        // causes UB
        //std::ptr::copy(hello.as_ptr(), s.array.as_mut_ptr() as *mut _, hello.len());      // OK

        dbg!(hello as *const _);           // [src\main.rs:15] hello          as *const _ = 0x00007ff7f2a9e3d8
        dbg!(hello.as_ptr() as *const _);  // [src\main.rs:16] hello.as_ptr() as *const _ = 0x00007ff7f2a9e3d8
    }

    dbg!(s);
}

(Playground)

  • The first copy causes UB, which is confirmed by Miri, and the output if dbg!(s) is garbled.
  • The second copy works as expected.

Why is that? Both debug lines below print the same address (e.g., 0x00007ff7f2a9e3d8), and I thought that a slice pointer can be automatically coerced to a pointer to the first element?

Type inference tripped you up. A pointer-to-array doesn't automatically become a pointer-to-element, so your types got inferred to be *const [u8; 12], which is too big. Being more explicit works under MIRI too:

std::ptr::copy(hello as *const _ as *const u8, s.array.as_mut_ptr() as *mut u8, hello.len());

In general, you should always be explicit with your types in unsafe.

4 Likes

The variable hello's type is &'static [u8; 12], so std::ptr::copy(hello, some_ptr, hello.len()) will copy 12 * 12 = 144 bytes. Copying 144 bytes from it indeed is UB.

1 Like

Ah yes, of course! Thanks!

In situations like this, it might be useful to be explicit in the type of the copy:

std::ptr::copy::<u8>(...);

This way, you're not relying on anything about the arguments to ensure that the operation is doing what you expect, and a reader can instantly see "this is copying a certain count of u8s". (I've found this approach useful in non-unsafe code using bytemuck, where even if there's no UB, converting the wrong type will result in the wrong result.)

5 Likes