rodrimati1992 / abi_stable_crates

Rust-to-Rust ffi,ffi-safe equivalents of std types,and creating libraries loaded at startup.
Apache License 2.0
528 stars 30 forks source link

Fn-types #73

Open mineichen opened 2 years ago

mineichen commented 2 years ago

I'm currently missing a stable_abi alternative for Fn, FnMut and FnOnce. I'm planning to implement those in the following style:

#[repr(C)]
#[derive(StableAbi)]
pub struct RBoxFnOnce<TParam, TResult> {
    caller: extern "C" fn(usize, TParam) -> TResult,
    remover: extern "C" fn(usize),
    inner: usize,
}

impl<T, TParam, TResult> From<T> for RBoxFnOnce<TParam, TResult> where T: FnOnce(TParam) ->TResult {
    fn from(inner: T) -> Self {
        let box_inner = Box::new(inner);
        let inner : usize = Box::into_raw(box_inner) as usize;

        extern "C" fn caller<T, TParam, TResult>(that: usize, param: TParam) -> TResult where T: FnOnce(TParam) -> TResult {
            let function = unsafe { Box::from_raw(that as *mut T) };
            (function)(param)
        }
        extern "C" fn dropper<T, TParam, TResult>(that: usize) where T: FnOnce(TParam) -> TResult {
            drop(unsafe { Box::from_raw(that as *mut T) });
        }
        Self {
            caller: caller::<T, TParam, TResult>,
            remover: dropper::<T, TParam, TResult>,
            inner
        }
    }
}

impl<TParam, TResult> Drop for RBoxFnOnce<TParam, TResult> {
    fn drop(&mut self) {
        if self.inner != 0 {
            (self.remover)(self.inner);
        }
    }
}
impl<TParam, TResult> RBoxFnOnce<TParam, TResult> {
    fn call(mut self, p: TParam) -> TResult {
        let inner = self.inner;
        self.inner = 0;
        (self.caller)(inner, p)
    }
}

Usage looks like:

fn api(f: impl Into<RBoxFnOnce<usize, usize>>) {
    assert_eq!(5, f.into().call(1));
}

#[test]
fn test () {
    let ctx = "Test".to_string();
    api(move |x| {
        let a = ctx;
        x + a.len()
    })
}

I'm not very experienced, but IMO this shouldn't have undefined behavior. If you are interested to integrate such functionality into stable_abi, I'm happy to fork your repo and push everything upstream eventually. Otherwise, I'll just create my own repo. Please let me know about your preferences.

marioortizmanero commented 2 years ago

So you're basically implementing closures manually so that they're FFI-safe? I think the only way to do this would be with a proc macro, right? Specially because we'd need variadic params in Fn's generics. But it might be a bit complicated, you'd have to know what variables are captured and etc. We could also use a hack like for tuples.

mineichen commented 2 years ago

Because "extern C fn" can be instrumented with generics within the from implementation, we don't need any macros. Just some pointer casts. You can find a working example here

I don't know if we have to implement variadic functions. For multiple parameters we could implement:

impl<T, TParam1, TParam2, TResult> From<T> for RBoxFnOnce<Tuple2<TParam1, TParam2>, TResult> where T: FnOnce(TParam1, TParam2) -> TResult;
afranchuk commented 2 years ago

@mineichen FWIW I did this as well, and though it's a bit cumbersome I had the same approach for multiple parameters (using tuples) and couldn't think of anything much better.