How to pass a closure as a C++ std::function?

This question is inspired by the topic of How to pass a closure as an extern C function , although I have not read the entire thread. Apologies if this is redundant.

The question is interesting for C++, because C++ does have "fat pointers" for closures (std::function). One can construct a C++ fat pointer from a C-style closure with std::bind(C-style function pointer, C-style context pointer).

I have been writing bindings for portions of a C++ library API (on an as-needed basis), mostly by wrapping it in a C ABI, which is easy to import/FFI from Rust. But I did not find a happy way to pass a Rust closure through a C ABI and into a C++ std::function for callback. (A large part of the difficulty is determining the correct lifetime for the callback closure and embedded refs, but let's call that off-topic for the question.)

Has anyone successfully done this before? What works? Thank you.

You likely need something similar to the following:

Rust side:

use std::ffi::c_void;

fn closure_to_parts<C>(closure: Box<C>) -> (*mut c_void, unsafe extern "C" fn(*mut c_void))
where C: FnMut()
{
   extern "C" fn call_closure<C: FnMut()>(c_ptr: *mut c_void) {
      let c = unsafe { &mut *(c_ptr as *mut C) };
      c();
   }

   (Box::into_raw(closure) as *mut c_void, call_closure::<C>)
}

C++ side:

#include <functional>
#include <utility>

struct CppObj {
   std::function<void ()> closure;
};

extern "C" void register_closure_parts(CppObj *obj, void *closure, void (*fn)(void *))
{
   // FIXME: if Drop is needed then a destructor should be taken as well like:
   // struct DroppableRustClosure {
   //    ~DroppableRustClosure() {
   //       if (free_fn != nullptr && closure != nullptr) {
   //          (free_fn)(std::exchange(closure, nullptr));
   //       }
   //    }
   //    void operator() {
   //       (call_fn)(closure);
   //    }
   //    void *closure = nullptr;
   //    void (*free_fn)(void *) = nullptr;
   //    void (*call_fn)(void *) = nullptr;
   // };

   obj->closure = [=]() { return (fn)(closure); };
}

Now you have everything in terms of C types and can complete the Rust <-> C <-> C++ shuffle. This example is pretty minimal, but should be able to be expanded pretty easily. There might be some noexcept tagging that can be done on the C++ side, but I'm not an expert there as I work with codebases without exceptions. In general exceptions across ffi boundaries are UB anyway. Additionally, you should add constraints for Send and Sync where appropriate depending on the how the closure is being used.

1 Like

@dylanhart Box means we're allocating this from the heap, yeah? I guess that means into_raw leaks the allocation when closure: Box<C> is dropped?

1 Like

Yes/no. Box::into_raw takes Box so it's a move and that's all handled internally. Yes the closure will be leaked without a destructor. free_fn as defined in the fixme code can be something like:

unsafe extern "C" fn free_closure<C>(c_ptr: *mut c_void) {
   let _ = Box::from_raw(c_ptr as *mut C);
}

Edit:

std::function also allocates, so providing a handler for std::bad_alloc or defining a custom allocator will be needed to handle OOM. IIRC rust doesn't handle OOM well, so this probably is a non-issue.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.