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?