Is there a sound way to call `ptr::copy_nonoverlapping` with `count > 1` and `dst` being an element of `Vec>`?

⚓ Rust    📅 2025-08-25    👤 surdeus    👁️ 5      

surdeus

Presuming I have a large (>= 64 KiB) v: Vec<UnsafeCell<u8>>. I'd like to store multiple bytes in v. v already is zero initialized, so there is 2^16 or more 0u8 in it.

How would I call ptr::copy_nonoverlapping(src, dst, count) (or its sibling ptr::copy_from_nonoverlapping) such that there is no UB, with count being (for the sake of an example) 16, and with an offset of 3 into v?

My naive (but according to stacked borrow checker in miri unsound) attempt was:

// init `v` with 64 KiB of null-bytes
let v_len = 0xffff;
let mut v: Vec<UnsafeCell<u8>> = Vec::with_capacity(v_len);
for _ in 0..v_len {
    v.push(UnsafeCell::new(0));
}

// in the real code, `v` is only ever accessible as shared ref.
let v = &v;

// offset into `v` where we want to store `src`
let offset = 3;

// actual datum that we want to store in `v`
let src = [1u8; 16];

// do the thing, without UB pls
unsafe {
    v.get_unchecked(offset)
        .get()
        .copy_from_nonoverlapping(src.as_ptr(), src.len())
}
Miri error message (click for more details)

I believe the issue to be, that miri correctly tracks that a given UnsafeCell<u8> (which I get via Vec::get_unchecked) only has pointer provenance to that single byte within v, and not the 15 following ones. However, I know that, given that offset + src.len() < v.len() there are enough further bytes after it for the copy_from_nonoverlapping to be completely contained within the allocation of v.

I understand that I can circumvent this by iterating over the UnsafeCell<u8> in v by hand, copying each byte individually (e.g. via ptr::copy_from_nonoverlapping with count == 1. Is there a better way?

Edit: in the non-minified context of the code I can only ever have a shared ref to v, thus any solution requiring &mut v unfortunately is off-limits. I did ask for the methods from ptr::* combined with UnsafeCell as they are fit for this purpose but forgot to explicitly name this constraint.

6 posts - 4 participants

Read full topic

🏷️ Rust_feed