Correct wrapper for externally-modified buffer

Hello,
I have a question regarding memory buffer that will be modified by an external source (e.g., a hardware device). I want the following code to be UB-free:

#[repr(C)]
struct Descriptor {
    buf: *mut u8,
}
extern "C" {
    fn start_dma_write_to_buffer(d: *mut Descriptor);
    fn wait_for_write_to_finish();
}

fn dma(buf: &mut [u8]) {
    let mut d = Descriptor { buf };
    unsafe {
        start_dma_write_to_buffer(&mut d);
        wait_for_write_to_finish();
    }
    // Caller shouldn't have to use volatile reads to access `buf` now
}

Related questions: (1) Correct wrapper for buffer filled by other process, and (2) Correct way to use memory mapped circular buffers. But I am still a bit confused after viewing these topics. In (1), it was mentioned that:

Are there now any references for this? If, like in the above code, the buffer was "indirectly" passed to the hardware via a descriptor, can the compiler still recognize that it has "leaked"? Does the safety here has anything to do with pointer provenance?

Thank you in advance.

Yes. The compiler knows that the pointer value buf has been passed

  • as a raw pointer, so it has no restrictions on usage (until fn dma returns and the &mut lifetime expires);
  • into code it can’t analyze the behavior of, so it can’t use any specific assumptions to optimize more than the default.

Optimizations based on assumptions of the usage of raw pointers depend on the compiler being able to see everywhere that pointer is actually used; here it cannot see. References have further restrictions that enable optimization in the absence of seeing everywhere, but you are, properly, using a raw pointer.[1]


  1. Though with sufficient cleverness, I think it would be possible to make your code use &mut instead of both pointers and still be correct. ↩︎

1 Like

Thank you for the detailed explanation!

That was really helpful.