Open dtolnay opened 4 years ago
I ran into this issue yesterday. Would be very useful to be able to pass a closure.
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 Arc
s 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);
});
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);
Currently if we have:
then Rust can call it as:
but not with any closure captures:
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.