I've created an FFI to interact with an external library. I think it's sound, but I'd love to get some more eyes on it, and more crucially, find out how I can verify that it isn't leaking memory without resorting to Valgrind, as I'm using macOS.
use std::f64;
use std::{mem, slice};
/// A C-compatible `struct` originating **outside** Rust
/// used for passing arrays across the FFI boundary
#[repr(C)]
pub struct ExternalArray {
pub data: *const libc::c_void,
pub len: libc::size_t,
}
/// A C-compatible `struct` originating **inside** Rust
/// used for passing arrays across the FFI boundary
#[repr(C)]
pub struct InternalArray {
pub data: *const libc::c_void,
pub len: libc::size_t,
}
impl From<Vec<[f64; 2]>> for InternalArray {
fn from(sl: Vec<[f64; 2]>) -> Self {
let mut v = sl;
v.shrink_to_fit(); // <-- this doesn't do what I thought it does
let array = InternalArray {
data: v.as_ptr() as *const libc::c_void,
len: v.len() as libc::size_t,
};
mem::forget(v);
array
}
}
impl Drop for InternalArray {
fn drop(&mut self) {
if self.data.is_null() {
return;
} else {
let _: Vec<[f64; 2]> =
unsafe { Vec::from_raw_parts(self.data as *mut [f64; 2], self.len, self.len) };
}
}
}
impl From<ExternalArray> for Vec<[f64; 2]> {
fn from(arr: ExternalArray) -> Self {
unsafe { slice::from_raw_parts(arr.data as *mut [f64; 2], arr.len).to_vec() }
}
}
impl From<InternalArray> for Vec<[f64; 2]> {
fn from(arr: InternalArray) -> Self {
// because this data originated in Rust we don't need to go via a slice in order to take ownership
unsafe { Vec::from_raw_parts(arr.data as *mut [f64; 2], arr.len, arr.len) }
}
}
The code is hopefully self-explanatory, but to explain its flow:
- The code is called by an external process which passes an
ExternalArray
to Rust; - Because this data is to be mutated in Rust, I have to take ownership of it;
- When I'm ready to return the result to the external caller I convert the
Vec<[f64; 2]>
to anExternalArray
,mem::forget
theVec
, and pass theExternalArray
back across the FFI; - When the external caller is finished with it, it's passed back a final time via
drop_internal_array
;
Another problem I may have is that the shrink_to_fit
call isn't actually guaranteeing what I think it is. Is there a better way of accomplishing this?