dtolnay / cxx

Safe interop between Rust and C++
https://cxx.rs
Apache License 2.0
5.83k stars 330 forks source link

Consider supporting closures/lambdas, not just function pointers #114

Open dtolnay opened 4 years ago

dtolnay commented 4 years ago

Currently if we have:

mod ffi {
    extern "C++" {
        fn f(callback: fn());
    }
}

then Rust can call it as:

ffi::f(|| {
    do_something();
});

but not with any closure captures:

// not allowed

let x = /* ... */;
ffi::f(|| {
    do_something(x);
});

This gets very complicated from a lifetimes perspective, not to mention a FnOnce vs Fn perspective, so there is a good chance that we don't end up supporting this. But it is worth exploring.

The workaround for now is to pass context through manually via an opaque Rust type.

mod ffi {
    extern "Rust" {
        type CallbackContext;
    }

    extern "C++" {
        fn f(
            callback: fn(ctx: Box<CallbackContext>),
            ctx: Box<CallbackContext>,
        );
    }
}

let x = /* ... */;
ffi::f(
    |ctx| {
        do_something(ctx);
    },
    Box::new(x),
);
myronahn commented 4 years ago

I ran into this issue yesterday. Would be very useful to be able to pass a closure.

LeonMatthesKDAB commented 2 years ago

Maybe at least the problem with lifetimes could be simplified by only accepting closures that have ownership of their contents (e.g. move || {}). That would still help in many cases, as you can then just pass Arcs around. Limiting to Fn for an initial implementation might also be enough.

So a minimum viable product could be accepting Box<impl Fn(...)->...>.

Which should at least allow you to do stuff like this:

let x = /* ... */;
ffi::f(move || {
    do_something(x);
});
minghuaw commented 8 months ago

Maybe at least the problem with lifetimes could be simplified by only accepting closures that have ownership of their contents (e.g. move || {}).

This seems to be already do-able (as a workaround) with trait object with the restriction that the trait cannot be external.

//! rust
pub trait Fun<Args> {
    type Output;

    fn execute(&self, args: Args) -> Self::Output;
}

impl<Args, F, R> Fun<Args> for F
where
    F: Fn(Args) -> R,
{
    type Output = R;

    fn execute(&self, args: Args) -> Self::Output {
        self(args)
    }
}

type DynFun = Box<dyn Fun<i32, Output=i32>>;

// Define a function to call `Fun::execute`
fn execute_dyn_fun(f: &DynFun, args: i32) -> i32 {
    f.execute(args)
}

#[cxx::bridge]
pub mod ffi {
    unsafe extern "C++" {
        include!("<your-header.h>");

        fn execute_in_cpp(f: &DynFun, args: i32) -> i32;
    }

    extern "Rust" {
        type DynFun;

        fn execute_dyn_fun(f: &DynFun, args: i32) -> i32;
    }
}
//! c++
int execute_in_cpp(const DynFun &f, int args) {
    return execute_dyn_fun(f, args);
}

Then you could execute a closure via ffi

//! rust
// Define a type that is not `Copy` or `Clone`.
struct Bar {
    s: String,
}

let bar = Bar { s: String::from("0123456") };
let f = move |x: i32| {
    if x > 0 {
        x + 1
    } else {
        bar.s.len() as i32
    }
};
let f = Box::new(f) as DynFun;
let y = ffi::execute_in_cpp(&f, 1);
assert_eq!(y, 2);

let y2 = ffi::execute_in_cpp(&f, -1);
assert_eq!(y2, 7);