Improving extern declarations
First of all, I like improving FFI just by "transmuting" their signatures (changing the API without changing the ABI ), so as to be able to explicitely state where nullable pointers may or may not be; which leads to being able to use references afterwards, once the implcit ownership / borrowing patterns from FFI become clearer:
use ::std::{*,
ptr::NonNull,
};
use ::libc::{
c_void,
};
mod ffi {
#[repr(C)]
pub
struct Handle {
opaque: [u8; 0],
}
extern "C" {
pub
fn new () -> Option<NonNull<Handle>>;
pub
fn delete (handle: &'_ mut Handle);
pub
fn get_state (handle: &'_ Handle) -> u64;
pub
fn exec (handle: &'_ Handle);
pub
fn register_callback (
handle: &'_ mut Handle,
data: Option<NonNull<c_void>>,
callback: unsafe extern "C" fn (handle: &Handle, data: Option<NonNull<c_void>>),
);
}
}
fn main ()
{
unsafe {
let mut handle: NonNull<ffi::Handle> = ffi::new().expect("new() failed");
dbg!(handle);
let handle: &mut ffi::Handle = handle.as_mut();
ffi::register_callback(
&mut *handle,
None,
Some({
unsafe extern "C" fn cb (handle: &ffi::Handle, data: Option<NonNull<c_void>>) {
println!("handle = {:p}", handle);
println!("data = {:?}", data);
}
cb
}),
);
println!("state = {}", ffi::get_state(&*handle));
ffi::exec(&*handle);
ffi::delete((move || handle)()); // prevent reborrow
}
}
The problem at hand
Now, you wish to wrap all this in a safer wrapper, while also storing the boxed closures in a vec in order to correctly free them. The issue is that you are doing both things at once, which is problematic.
I suggest you first wrap all the functionality but the callback-registering in a first wrapper, and then create a struct stitching together both the wrapper and the vec. By making the struct deref to the wrapper, you get the same ergonomics, and you just have to add the callback registering to the struct. This is one way to solve your problem:
#[derive(Debug)]
#[repr(transparent)]
struct MyHandle (
NonNull<ffi::Handle>,
);
impl MyHandle {
#[inline]
pub
fn new () -> MyHandleWithCallbacks
{ unsafe {
MyHandleWithCallbacks {
handle: Self(ffi::new().expect("new() failed")),
callbacks: vec![],
}
}}
#[inline]
pub
fn state (self: &'_ Self) -> u64
{ unsafe {
ffi::get_state(self.0.as_ref())
}}
pub
fn exec (self: &'_ Self)
{ unsafe {
ffi::exec(self.0.as_ref())
}}
}
impl Drop for MyHandle {
fn drop (self: &'_ mut Self)
{ unsafe {
ffi::delete(self.0.as_mut());
}}
}
struct MyHandleWithCallbacks {
handle: MyHandle,
callbacks: Vec<Box<dyn Fn (&MyHandle) + 'static>>,
}
impl ops::Deref for MyHandleWithCallbacks {
type Target = MyHandle;
#[inline]
fn deref (self: &'_ Self) -> &'_ Self::Target
{
&self.handle
}
}
impl ops::DerefMut for MyHandleWithCallbacks {
#[inline]
fn deref_mut (self: &'_ mut Self) -> &'_ mut Self::Target
{
&mut self.handle
}
}
impl MyHandleWithCallbacks {
pub
fn add_callback<Closure> (
self: &'_ mut Self,
boxed_closure: Box<Closure>,
)
where
Closure : 'static,
Closure : Fn(&MyHandle),
{
unsafe extern "C"
fn c_callback<Closure> (
ffi_handle: &'_ ffi::Handle,
data: Option<NonNull<c_void>>,
)
where
Closure : Fn(&MyHandle) + 'static,
{
::scopeguard::defer_on_unwind! {{
eprintln!("Caught Rust unwinding accross FFI, aborting...");
::std::process::abort();
}}
let closure =
data.expect("Error, got NULL data")
.cast::<Closure>()
;
let at_my_handle = mem::transmute::<
& &ffi::Handle,
& MyHandle, // thanks to #[repr(transparent)]
>(&ffi_handle);
closure.as_ref()(at_my_handle);
}
let data = Some(NonNull::cast::<c_void>(
NonNull::from(&*boxed_closure)
));
unsafe {
ffi::register_callback(
self.handle.0.as_mut(),
data,
c_callback::<Closure>,
);
}
self.callbacks.push(
boxed_closure
/* as Box<dyn Fn(&MyHandle) + 'static> */
);
}
}
fn main ()
{
let mut handle = MyHandle::new();
let closure = {
let x = cell::Cell::new(0);
move |handle: &MyHandle| {
if x.replace(dbg!(x.get()) + 1) < 5 {
dbg!(handle.state());
handle.exec();
}
}
};
handle.add_callback(Box::new(closure));
handle.exec();
}
-
if you are to always box an input, asking for it to be boxed beforehand avoids unneeded boxing (imagine someone already having a boxed closure);
-
unless the callback code is refactored into using CSP (e.g., using nested closures instead of classic procedural style), you cannot enforce that that closures borrows last until
Handle
; hence the'static
requirement. So you will needmove
closures.
NB: the extern "C" fn adapter<F>
is a pretty neat pattern, well found!