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.
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.
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.
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 :
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()
}
#[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()
}
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:
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.
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
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.