rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.23k stars 12.7k forks source link

Support coercing non-capturing closures to extern function pointers #44291

Open joshtriplett opened 7 years ago

joshtriplett commented 7 years ago

This is a followup to https://github.com/rust-lang/rust/issues/39817 . Non-capturing closures now coerce to function pointers, but not to extern fn pointers, as provided to C functions expecting a callback. Adding support for this would make it much simpler to call a C function and provide an appropriate callback inline.

(I'm also curious what it would take to make a capturing closure work as an extern function pointer, but that's a separate issue.)

jethrogb commented 7 years ago

(I'm also curious what it would take to make a capturing closure work as an extern function pointer, but that's a separate issue.)

You don't. It basically only works for callback functions that take a data pointer (which many do). Then you can do something like this (highly unsafe):

#![feature(fn_traits, unboxed_closures)]

fn closure_to_fn_and_data<A, F: Fn<A> + 'static>(
    f: F,
) -> (
    unsafe extern "C" fn(*const Box<Fn<A, Output = F::Output>>, A) -> F::Output,
    *const Box<Fn<A, Output = F::Output>>,
) {
    unsafe extern "C" fn callback<A, F: Fn<A> + 'static>(
        closure: *const Box<Fn<A, Output = F::Output>>,
        args: A,
    ) -> F::Output {
        std::ops::Fn::call(&**closure, args)
    }

    (
        callback::<A, F>,
        Box::into_raw(Box::new(Box::new(f) as Box<Fn<A, Output = F::Output>>)) as _,
    )
}
est31 commented 7 years ago

(I'm also curious what it would take to make a capturing closure work as an extern function pointer, but that's a separate issue.)

@joshtriplett could you factor out this question from this issue? I'd like to comment on that as well but I dont want to pollute this discussion

dtolnay commented 7 years ago

What happens if the same closure is coerced to both fn and extern fn? Do I end up with two monomorphizations of the closure, one for each calling convention?

fn joshtriplett(hi: bool) {
    let closure = || ();
    if hi {
        let _rust_fn: fn() = closure;
    } else {
        let _c_fn: extern fn() = closure;
    }
}
eddyb commented 7 years ago

@dtolnay I'd expect a thin shim, and you'd get a duplicated closure body only if LLVM inlines it.

steveklabnik commented 5 years ago

Triage: I don't believe there's been any change here. I'd expect this to work:

const foo: [extern fn(&mut u32); 1] = [
  |var: &mut u32| {},
];

But it does not:

error[E0308]: mismatched types
 --> src/lib.rs:2:3
  |
2 |   |var: &mut u32| {},
  |   ^^^^^^^^^^^^^^^^^^ expected "C" fn, found "Rust" fn
  |
  = note: expected type `for<'r> extern "C" fn(&'r mut u32)`
             found type `[closure@src/lib.rs:2:3: 2:21]`
Dylan-DPC-zz commented 5 years ago

The PR has been closed due to inactivity so it is free if anyone wants to pick it up