I'm exposing a Rust crate to C and I'm trying to figure out how to copy a Vec<u8> into a *mut u8 given by the C world as a parameter like this:
pub extern "C" fn c_create_data(data: *mut u8) -> c_int {
let v: Vec<u8> = some_data_gen_fn();
// how to put the content of the Vec so it's available to C?
v.len()
}
Sorry, my function def is not complete in my example:
pub extern "C" fn c_create_data(data: *mut u8, data_len: c_uint) -> c_int {
let v: Vec<u8> = some_data_gen_fn();
if v.len() > data_len {
return -1;
}
// how to put the content of the Vec so it's available to C?
v.len()
}
I'm checking that the declared buffer size is enough of course
Oh, easy. A Vec owns its memory, and we’re owning the Vec here which means there can be absolutely nothing else pointing to accessing the same region as v.as_ptr() with length v.len() (unless something has gone horribly wrong in a different place already, but that’s why such things gone horribly wrong are "undefined behavior").
The biggest problem with translating this is converting the integers without panicking (panicking across FFI boundaries is UB).
Here's what I came up with so far:
pub unsafe extern "C" fn c_create_data(data: *mut u8, data_len: c_uint) -> c_int {
let v: Vec<u8> = some_data_gen_fn();
if usize::try_from(data_len)
.map(|len| v.len() > len)
.unwrap_or(false) // if data_len can't be converted to usize, that's fine, we just won't use the whole thing
{
return -1;
}
std::ptr::copy_nonoverlapping(v.as_ptr(), data, v.len());
c_int::try_from(v.len()).unwrap_or(-1) // note: I don't think this can ever actually return -1
}
I believe that slice::from_raw_parts_mut is technically OK here due to Rust and C not really sharing a notion of "initializedness", but ptr::copy_nonoverlapping is shorter, anyway.
The extra complexity is mostly because of the width mismatch between c_uint and usize. It would be cleaner if the function can be rewritten to take size_t instead.
Note also that the whole function needs to be marked unsafe, because it's possible to create raw pointers in safe code.
This problem also goes away if you change c_create_data to take usize and return isize (aka size_t and off_t, respectively), because a Vec never allocates more than isize::MAX bytes.
(Although Vec guarantees that by panicking in cases, which could be bad if it happens in some_data_gen_fn.)
You have them available on the ::libc crate: ::libc::size_t.
That being said, since Rust "only" supports platforms where uintptr_t == size_t, you can use usize as size_t. off_t is more subtle, on the other hand, so should you need it, use the one from ::libc.