How to return byte array from Rust function to FFI C?


#1

I’m integrating my Rust library with multiple languages, and for basic types like int, bool it works great, but how to pass something like &[u8] or Vec<u8> ?
For example I have similar function like this

pub fn generate_data() -> Vec<u8> {
   // returning Vec<u8>
}

And I need to call this from C/C++ and receive byte array. Main concern for me: is it going to mess with Rust’s memory model or it is doable without unsafe thing?
Thanks!


#2

You can’t take data pointer from C and return it to Rust as Vec. Only Rust can allocate a Vec, because it’s always freed using Rust’s own private allocator. If you want to return a Vec, you’ll have to copy the data into it first.

There’s CVec for allowing Rust to use malloc-allocated data.

&[u8] is a type that means “you never ever have to worry about freeing it”, so you can return it from a function only as &'static [u8] if C leaked that memory or it’s from a global/static variable in C, but that’s rather rare.


#3

thanks for replay @kornel
So I can write something like this

pub fn generate_data() -> &'static [u8] {
   // returning &[u8]
}

And use it as a byte buffer pointer from C/C++ ?
If I got it right, ownership for the data passed with static lifetime would be on C/C++ for memory cleanup right?


#4

If you want to return Rust allocated memory, then you’ll need to export a function to free it as well, which the C code can call. Here’s a quick example:

#[repr(C)]
struct Buffer {
    data: *mut u8,
    len: usize,
}

extern "C" fn generate_data() -> Buffer {
    let mut buf = vec![0; 512].into_boxed_slice();
    let data = buf.as_mut_ptr();
    let len = buf.len();
    std::mem::forget(buf);
    Buffer { data, len }
}

extern "C" fn free_buf(buf: Buffer) {
    let s = unsafe { std::slice::from_raw_parts_mut(buf.data, buf.len) };
    let s = s.as_mut_ptr();
    unsafe {
        Box::from_raw(s);
    }
}

You may want to consider having the Rust code take an externally allocated buffer instead, so that its (de)allocation is handled elsewhere.

As noted upthread, the important thing is to not mix up the different allocators.


#5

Sorry, I misread which direction you want to pass the data.

C doesn’t understand Rust slices, so you can’t give them to C at all. For passing data to C you have to use raw C pointers, like *const u8.

But careful with raw pointers, because they are unsafe, just like in C. Use-after-free and dangling pointers to stack variables are possible. So when you get a pointer to a Rust object, you must ensure it’s not a temporary on stack (i.e. use Box to allocate it on the heap), and make sure Rust won’t free it while C is still using it (that’s why @vitalyd’s example has mem::forget()).

Box::into_raw() and Box::from_raw() is a good pair giving pointers to C and getting them back to release the memory.

Lifetimes don’t pass ownership. Lifetimes don’t do anything in a running program. Lifetimes only describe to the compiler what would happen to the memory anyway (they’re like assert()). 'static informs the compiler that nobody will free this memory, it’s leaked and there’s no cleanup.