Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use cbindgen to return and free a Box<Vec<_>>?

Tags:

rust

ffi

I have a struct returned to C code from Rust. I have no idea if it's a good way to do things, but it does work for rebuilding the struct and freeing memory without leaks.

#[repr(C)]
pub struct s {
    // ...
}

#[repr(C)]
#[allow(clippy::box_vec)]
pub struct s_arr {
    arr: *const s,
    n: i8,
    vec: Box<Vec<s>>,
}

/// Frees memory that was returned to C code
pub unsafe extern "C" fn free_s_arr(a: *mut s_arr) {
    Box::from_raw(s_arr);
}

/// Generates an array for the C code
pub unsafe extern "C" fn gen_s_arr() -> *mut s_arr {
    let many_s: Vec<s> = Vec::new();
    // ... logic here

    Box::into_raw(Box::new(s_arr {
        arr: many_s.as_mut_ptr(),
        n: many_s.len() as i8,
        vec: many_s,
    }))
}

The C header is currently written by hand, but I wanted to try out cbindgen. The manual C definition for s_arr is:

struct s_arr {
    struct s *arr;
    int8_t n;
    void *_;
};

cbindgen generates the following for s_arr:

typedef struct Box_Vec_s Box_Vec_s;

typedef struct s_arr {
        const s *arr;
        int8_t n;
        Box_Vec_s vec;
} s_arr;

This doesn't work since struct Box_Vec_s is not defined. Ideally I would just want to override the cbindgen type generated for vec to make it void * since it requires no code changes and thus no additional testing, but I am open to other suggestions.

I have looked through the cbindgen documentation, though not the examples, and couldn't find anything.

like image 763
MrTheFoolish Avatar asked Oct 16 '25 11:10

MrTheFoolish


1 Answers

Your question is a bit unclear, but I think that if I understood you right, you're confusing two things and being led down a dark alley as a result.

In C, a dynamically-sized array, as you probably know, is identified by two things:

  1. Its starting position, as a pointer
  2. Its length

Rust follows the same convention - a Vec<_>, below the hood, shares the same structure (well, almost. It has a capacity as well, but that's beside the point).

Passing the boxed vector on top of a pointer is not only overkill, but extremely unwise. FFI bindings may be smart, but they're not smart enough to deal with a boxed complex type most of the time.

To solve this, we're going to simplify your bindings. I've added a single element in struct S to show you how it works. I've also cleaned up your FFI boundary:

#[repr(C)]
#[no_mangle]
pub struct S {
    foo: u8
}

#[repr(C)]
pub struct s_arr {
    arr: *mut S,
    n: usize,
    cap: usize
}

// Retrieve the vector back
pub unsafe extern "C" fn recombine_s_arr(ptr: *mut S, n: usize, cap: usize) -> Vec<S> {
    Vec::from_raw_parts(ptr, n, cap)
}

#[no_mangle]
pub unsafe extern "C" fn gen_s_arr() -> s_arr {
    let mut many_s: Vec<S> = Vec::new();

    let output = s_arr {
        arr: many_s.as_mut_ptr(),
        n: many_s.len(),
        cap: many_s.capacity()
    };
    std::mem::forget(many_s);
    output
}

With this, cbindgen returns the expected header definitions:

typedef struct {
  uint8_t foo;
} so58311426S;

typedef struct {
  so58311426S *arr;
  uintptr_t n;
  uintptr_t cap;
} so58311426s_arr;

so58311426s_arr gen_s_arr(void);

This allows us to call gen_s_arr() from either C or Rust and retrieve a struct that is usable across both parts of the FFI boundary (so58311426s_arr). This struct contains all we need to be able to modify our array of S (well, so58311426S according to cbindgen).

When passing through FFI, you need to make sure of a few simple things:

  • You cannot pass raw boxes or non-primitive types; you will almost universally need to convert down to a set of pointers or change your definitions to accomodate (as I have done here)
  • You most definitely do not pass raw vectors. At most, you pass a slice, as that is a primitive type (see the point above).
  • You make sure to std::mem::forget() whatever you do not want to deallocate, and make sure to remember to deallocate it or reform it somewhere else.

I will edit this question in an hour; I have a plane to get on to. Let me know if any of this needs clarifications and I'll get to it once I'm in the right country :-)

like image 64
Sébastien Renauld Avatar answered Oct 19 '25 02:10

Sébastien Renauld