Pass a Vec from Rust to C

For a project, I have to build a library that interfaces with a C program.

I'm thinking about how to do it, I don't have any code to show yet.

The idea is that the Rust library does its job and builds a Vec, it passes it to the C program, which interfaces with it.

I'm not sure how to do this.

Thank you.

#[repr(C)]
pub struct MyStruct {
    pub value : ::std::os::raw::c_int, 
    ......
}

pub extern "C" fn MakeJob(???) -> ??? {
    
    let my_struct = Vec::new();
    for struct_to_export in .... {
        my_struct.push(struct_to_export);
    }
    // export my_struct to C
    ....
}
???? MakeJob(????); 
typedef struct My_Struct
{
    int value;
    ....
} My_Struct;

void main(void){
    // call to MakeJob from Rust lib
    // 
    My_Struct ??? // Retrieve from Rust
    
}

You want to pass a pointer to the Vec to C, along with its length. The simplest way to obtain the pointer is probably via Vec::into_boxed_slice() and Box::into_raw(). Note that the C code must not attempt to deallocate the pointer, since it comes from a different allocator. Instead, the pointer should be passed to a Rust function that reconstructs the Box using Box::from_raw and then drops it, to ensure it gets deallocated by Rust's allocator.

You need to split the vector into its three components with the following methods:

Once you have done this, you can use mem::forget to destroy the vector without running its destructor. Then you just pass the three components to C, and C will be able to access the nth element via the pointer.

To destroy the vector, the C code must pass it back to Rust. Rust can then destroy it by rebuilding it using Vec::from_raw_parts. It is not possible to allocate, reallocate or deallocate the vector with the malloc/realloc/free methods in C. All of that must be done by passing it back to Rust. On the other hand, changing the length in C is perfectly fine as long as it doesn't grow larger than the capacity.

@jameseb7 Be aware that into_boxed_slice() can be troublesome. It produces a fat pointer which must be split and put back together properly.

5 Likes

That is true. I was going to suggest the as_ptr() approach, then figured into_boxed_slice() would be better to avoid the mem::forget(), but forgot the need for an extra cast to thin the pointer.

I was surprised that there isn't a Vec::into_ptr(). There's Vec::into_raw_parts(), but that's not stable yet.

The challenge with into_boxed_slice() is that to pass it to C, you must take the *mut [T] you got from Box::into_raw and split it into:

  • A pointer to the first element. Should have the type *mut T.
  • It's length.

Then to deallocate it in Rust, you take those two pieces and turn them back into a *mut [T] using std::ptr::slice_from_raw_parts_mut. Only at this point can you use Box::from_raw. Because of this, it is not much simpler than the solution that uses Vec directly besides the fact that you don't need to store the capacity separately.

1 Like

For this task I heavily recommend you use safer-ffi, this way you won't have to write unsafe code on the Rust side, and will have a header generation for free :slightly_smiling_face: :

image

src/lib.rs

  #![allow(nonstandard_style] // to use other casing conventions unhindered.

+ use ::safer_ffi::prelude::*;

+ #[derive_ReprC]
  #[repr(C)]
  pub struct MyStruct {
      pub value : ::std::os::raw::c_int, 
      // ...
  }

- #[no_mangle] pub /* unsafe */ extern "C'
+ #[ffi_export]
  fn MakeJob ()
+   -> repr_c::Vec<MyStruct>
  {    
      let mut my_structs = Vec::new();
      for struct_to_export in ... {
          my_structs.push(struct_to_export);
      }
      // export my_struct to C
+     my_structs.into()
  }

Cargo.toml

[lib]
crate-type = [ "staticlib" ] # or cdylib
[dependencies.safer-ffi]
version = "0.0.5"
features = [ "proc_macros" ]

Header generation

src/lib.rs

#[cfg(feature = "generate-headers")]
#[test]
fn generate_headers ()
  -> ::std::io::Result<()>
{
    ::safer_ffi::headers::builder()
        .to_file("headers.h")?  // Feel free to rename to your liking (you could even read from an env var)
        .generate()
}

Cargo.toml

[features]
generate-headers = ["safer-ffi/headers"]

Shell

cargo test --features generate-headers -- generate_headers

headers.h then contains:

/*! \file */
/*******************************************
 *                                         *
 *  File auto-generated by `::safer_ffi`.  *
 *                                         *
 *  Do not manually edit this file.        *
 *                                         *
 *******************************************/

#ifndef __RUST_CRATENAME__
#define __RUST_CRATENAME__

#ifdef __cplusplus
extern "C" {
#endif


#include <stddef.h>
#include <stdint.h>

typedef struct {

    int32_t value;

} MyStruct_t;

/** \brief
 *  Same as [`Vec<T>`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout
 */
typedef struct {

    MyStruct_t * ptr;

    size_t len;

    size_t cap;

} Vec_MyStruct_t;

Vec_MyStruct_t MakeJob (void);


#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* __RUST_CRATENAME__ */


Quid of freeing the vec?

In this example you are giving ownership of the Vec to the (FFI) caller, but the FFI has no way of freeing the Vec contents correctly. So you should add a freeing function that just gives the ownership back to the Rust side, which will then free it with an implicit or explicit drop:

#[ffi_export]
fn free_vec (vec: repr_c::Vec<MyStruct>)
{
    drop(vec);
}
3 Likes

I would suggest to pass a slice to the C function, and if it wants to keep the data it has to copy it.

C

void some_func(uint32* data, size_t len) {
   // can use `data` during this function call.
   // the pointer is not valid after the function returns
}

Rust

extern "C" {
    fn some_func(*const u32, usize);
}
fn call_some_func(data: &[u32]) {
    unsafe {
        some_func(data.ptr(), data.len());
    }
}
1 Like

Hello,

Thank you for your answers,

I was implementing Alice's answer, when I saw Yandros's answer with the crate ::safe_ffi, very simple to understand and to implement.

I give you the example code I made:

#include "rust_rects.h"
#include <stdio.h>

void main(void){

    Vec_Rect_t myRect = hello_from_rust();
    for(int i=0;i<myRect.len;i++){
        printf("%d ",i);
        Rect_t *tmp_rect = myRect.ptr++;
        printf("%d\n",tmp_rect->x);
    }
    free_vec(myRect);
}
use ::safer_ffi::prelude::*;
#[derive_ReprC]
#[repr(C)]
#[derive(Debug, Clone, Copy)]

pub struct Rect {
    pub x : ::std::os::raw::c_int, 
    pub y : ::std::os::raw::c_int, 
    pub w : ::std::os::raw::c_int, 
    pub h : ::std::os::raw::c_int, 
}

#[ffi_export]
pub extern "C" fn hello_from_rust() -> repr_c::Vec<Rect> {
    println!("hello from rust");
    let mut lst_rect = Vec::new();
    let my_rect = Rect {
        x : 0,
        y : 0,
        w : 128,
        h : 82,
    };

    lst_rect.push(my_rect);
    let my_rect = Rect {
        x : 127,
        y : 0,
        w : 128,
        h : 82,
    };
    lst_rect.push(my_rect);
    println!("rect : {:?}",lst_rect);
    lst_rect.into()
}

#[ffi_export]
fn free_vec (vec: repr_c::Vec<Rect>)
{
    drop(vec);
}
/// The following test function is necessary for the header generation.
#[::safer_ffi::cfg_headers]
#[test]
fn generate_headers() -> ::std::io::Result<()> {
    ::safer_ffi::headers::builder()
        .to_file("rust_rects.h")?
        .generate()
}

the Header is generated by ::safe_ffi, so I don't put it.

Great job, it allowed me to understand better the solution of Alice.

@s3bk, I don't need to keep the data once the work is done by the Rust lib. But it could be useful for later.

2 Likes

That looks good!

My proposal can't work here as Rust has to return a Vec/allocation to C.

bad news, ::safe_ffi, does not work with a function of an impl (&self)

#[ffi_export]
pub extern "C" fn hello_from_rust(&self) -> repr_c::Vec<Rect> {
    println!("hello from rust");
    let mut lst_rect = Vec::new();
    let my_rect = Rect {
        x : 0,
        y : 0,
        w : 128,
        h : 82,
    };

    lst_rect.push(my_rect);
    let my_rect = Rect {
        x : 127,
        y : 0,
        w : 128,
        h : 82,
    };
    lst_rect.push(my_rect);
    println!("rect : {:?}",lst_rect);
    lst_rect.into()
}
no rules expected the token `&`
   --> src/lib.rs:102:22
    |
102 |     pub fn hello_from_rust(&self) -> repr_c::Vec<HxObject> {
    |                            ^ no rules expected this token in macro call

error: aborting due to previous error

Why take &self? It isn't used at all.

You can also always write

struct Foo {}
impl Foo {
    pub fn bar(&self, arg: u32) { ... }
}

as

fn foo_bar(foo: &Foo, arg: u32) {
   foo.bar(arg);
}

It's because, by example Foo is not implemented by ::safe_ffi (i suppose)

#[ffi_export]
    |       ^^^^^^^^^^^^^ the trait `ReprC` is not implemented for `Foo`

How do you expect to work it when taking &self, then? Method definition is essentially a syntax sugar, semantically it's no different from the free function with &Self being the first argument.

1 Like