I open this topic because I struggle to understand a solution proposed in this old topic that has been closed.
The proposed solution is:
#[repr(C)]
struct Buffer {
data: *mut u8,
len: usize,
}
extern "C" fn generate_data() -> Buffer {
// alloc 512 bytes in the heap
let mut buf = vec![0; 512].into_boxed_slice();
// get a pointer to the first byte
let data = buf.as_mut_ptr();
let len = buf.len();
// tell rustc to not deallocate the above allocated bytes
std::mem::forget(buf);
Buffer { data, len }
}
extern "C" fn free_buf(buf: Buffer) {
// create a slice to the above allocated bytes
let s = unsafe { std::slice::from_raw_parts_mut(buf.data, buf.len) };
// get a pointer to the first byte
let s = s.as_mut_ptr();
// create a box that point to the first byte and drop it
unsafe {
let _: Box<u8> = Box::from_raw(s);
}
}
I enhanced the proposed solution with some comments containing what I think the code is doing.
Now my questions:
Shouldn't the last unsafe block only drop the first byte?
Can you explain how rustc is going to free the memory?
If you create a Box<u8> that is indeed wrong. You need to create an Box<[u8]>. You can do that with std::ptr::slice_from_raw_parts_mut, which returns an *mut [u8].
I'm a bit paranoid when it comes to UB. I'd write the stable version as follows:
fn generate_data() -> Buffer {
// I prefer this over `forget`, because it's less error prone.
// Fewer necessary safety checks = happier developer
let mut buf = std::mem::ManuallyDrop::new(vec![0_u8; 512]);
// Get the length, first, then the pointer (doing it the other way around **currently** doesn't cause UB, but it may be unsound due to unclear (to me, at least) guarantees of the std lib)
let len = buf.len();
let data = buf.as_mut_ptr();
Buffer { data, len }
}
extern "C" fn free_buf(buf: Buffer) { /* unchanged */ }
Sorry, I don't really see how the opposite order could go wrong. What did you have in mind? I.e. what guarantees doesn't the stdlib make that could result in UB by getting the pointer first and the length last?
.as_mut_ptr() returns *mut T from the &mut Vec<T>. But .len() requires &Vec<T> which may invalidates previously taken &mut Vec<T> and indirections derived from it.
Well it's hard to give just an example since it's not much about the runtime operation. It's about (not formally specified but proposed) the Rust memory model. It means how the theoretical abstract Rust machine defines what the pointers and references means. If you want to dig more, it would be helpful to read this blog post and all the posts of the blog.
Heh, I find it to be a very apt coincidence here, that the detailed unsafe solution of that post was wrong (freeing a Box<u8>): using unsafe is dangerous and error-prone, typos such as Box<u8> instead of Box<[u8]> suffice to cause UB without necessarily triggering compiler errors!
Indeed, another example is what @Phlopsi mentioned: even if you get the Box<[u8]> part right, you'd still have the potential issue, w.r.t. aliasing, of using mem::forget instead of a preemptiveManuallyDrop.