Support coercing non-capturing closures to extern function pointers

a4dcba6
Opened by Josh Triplett at 2024-09-05 20:55:28

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

  1. (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 _,
        )
    }
    

    jethrogb at 2017-09-03 16:27:35

  2. (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

    est31 at 2017-09-03 17:40:59

  3. 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;
        }
    }
    

    David Tolnay at 2017-09-03 18:11:26

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

    Eduard-Mihai Burtescu at 2017-09-03 18:33:55

  5. 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]`
    
    

    Steve Klabnik at 2019-03-10 23:03:10

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

    Dylan DPC at 2019-07-22 12:32:59