Callbacks and Function Pointers across the FFI boundary [Feedback Wanted]

I've just added a page on doing callbacks and function pointers to this FFI guide I'm working on and was wanting some feedback.

I've tried to represent a semi-realistic scenario where you're using some C library that will run an expensive computation and lets you give it a callback to run on the intermediate results (allowing you to monitor the computation and abort it early).

Then as the second part I've changed the callback a bit to use a struct method so you can keep track of what happened each time the callback was called.

In particular I'm wondering:

  • Is this example something realistic you might see/do when interacting with other libraries across the FFI boundary?
  • Can it be improved or altered to make the concept easier to understand (and prevent unnecessary footguns)?
  • Can you see any typos, ambiguous wording, or other grammatical errors?

Some Links:

3 Likes

Another useful pattern I've seen with stateful callbacks is just using the state pointer to store a pointer to a Rust closure and executing that inside the callback.

That's actually what I was planning to do in the "Stateful Callbacks" bit, but I found that passing around a boxed closure and then getting a reference to closure as a function pointer a bit difficult without using some sort of shim.

I ended up defining something like this:

unsafe extern "C" fn shim<F>(closure: *mut F, intermediate_result: c_int) -> c_int 
where F: FnMut(c_int) -> c_int
{
  ...
}

But I found the juggling and boxing required to use the closure a bit difficult and felt it'd probably create more problems than it's worth.

If you know a way to pass a Rust closure into my stateful_expensive_calculation() function which is easy for users though, I'd definitely want to add that to the page.

Yep, I'll throw together an example of how I did it last time I had to.

Here's an example using FnMut, it's a bit more complicated as it adds in panic safety since it runs arbitrary user code (unwinding across an FFI boundary is undefined behaviour). If this was being used in a library that wraps a lot of functions that take callbacks then most of the functionality here could be pulled out into reusable building blocks and each function should stay relatively readable.

1 Like

That's a good point about the panic safety. I guess regardless you're going to need some sort of shim/wrapper which can execute your closure or function and then catch any panics so you don't unwind across the FFI boundary, aren't you?