Suppose I have a C++ allocated buffer uint8_t* and I want to access it from Rust:
One way would be to have the C++ function:
uint8_t receive(uint8_t** data, size_t* size) {
//allocates the data, writes to it and then points *data to it
return 0;//on success
}
however this leaves Rust responsible for dealocating the data. Same for
uint8_t* receive(size_t* size) {
uit8_t* data = //allocates data
*size = data_size;
return data;
}
The idea I have is to allocate the data on Rust and then pass a pointer for C++ to fill:
uint8_t receive(uint8_t* data, size_t size) {
//fills data up to size
return 0;
}
However, we cannot simply allocate a buffer in Rust and expect it to be C/C++ compatible. Also it has the limitation of having to know a size in advance, or use a sufficiently big size for all buffers.
You can make Rust deallocate the data if you provide an FFI based function that internally deallocates it. Then Rust can call that function with the pointer, deallocating it properly. You can even make a custom Rust struct that does this in its destructor.
Also an option, as long as the correct function to free the data is documented in the api.
Then you could create a custom struct in Rust that holds the data:
struct CBuf {
ptr: *mut u8,
size: usize
}
impl Deref for CBuf {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(self.ptr, self.size)
}
}
}
impl Drop for CBuf {
fn drop(&mut self) {
unsafe {
// from C
free(self.ptr);
}
}
}
IIUC, there's the potential for UB here if the C and Rust programs use different memory allocators. Memory has to be deallocated by the same allocator that allocated it.
Granted, it's not very often that you decide to use a different allocator, even more so for only one of the programs. But it's worth noting.
It's quite common for a C API to provide XXX_free() functions which clean up a complex business object, so we'd just extend that to our buffer.
In this case you just need to make sure that the library which creates an object also provides a destructor, and you won't have any allocator mismatch problems.
You'll naturally fall into this explicit destructor pattern when writing FFI code (e.g. because you may need to pass ownership of Rust types to C), so it ends up being a non-issue in practice.