Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fn-types #73

Open
mineichen opened this issue Jan 7, 2022 · 3 comments
Open

Fn-types #73

mineichen opened this issue Jan 7, 2022 · 3 comments

Comments

@mineichen
Copy link

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
Copy link
Contributor

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
Copy link
Author

mineichen commented Jan 7, 2022

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
Copy link
Contributor

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants