Related to Is #[repr(C)] necessary for Rust-to-Rust FFI
I made this prototype code for passing a Box<dyn Fn>
over FFI boundaries safely. However, I'm wondering if there's an existing crate which implements this, since I assume I'm not the first to think of it. Or, alternatively, if I'm taking the wrong approach or more code is terribly misguided, feel free to reframe the question.
/// Wrapper around a Box<dyn Fn(I) -> O> that is FFI-safe, assuming that
/// I and O are themselves FFI-safe.
///
/// Limitations:
/// - Input/output cannot contain references
/// - Other versions must be made for `FnMut` and `FnOnce`
#[repr(C)]
pub struct ExternFn<I, O> {
ctx: *mut u8,
call: unsafe extern "C" fn(*mut u8, I) -> O,
drop: unsafe extern "C" fn(*mut u8),
}
impl<I, O> ExternFn<I, O> {
/// Wrap a closure.
pub fn new<F: Fn(I) -> O>(closure: F) -> Self {
let ctx = Box::into_raw(Box::new(closure)) as *mut u8;
unsafe extern "C" fn call<I, O, F: Fn(I) -> O>(ctx: *mut u8, input: I) -> O {
let closure: &F = &*(ctx as *mut F);
closure(input)
}
unsafe extern "C" fn drop<F>(ctx: *mut u8) {
let closure: Box<F> = Box::from_raw(ctx as *mut F);
std::mem::drop(closure);
}
ExternFn {
ctx,
call: call::<I, O, F>,
drop: drop::<F>,
}
}
/// Call the function.
pub fn call(&self, input: I) -> O {
unsafe { (self.call)(self.ctx, input) }
}
}
impl<I, O> Drop for ExternFn<I, O> {
fn drop(&mut self) {
unsafe { (self.drop)(self.ctx) }
}
}