That's a pity. Know that in that case, @krdln solution is unsound, since the captured pointer ends up dangling when the rusty wrapper is dropped.
Sometimes there is at most one callback to be registered, in which case you can override a registered callback with a dummy no-op one, which acts as deregistering it.
Otherwise, unless you have access to some other guarantees, you should be conservative and take an &'static (impl Sync + Fn())
parameter, i.e., a: <F : 'static + Sync + Fn> f: &'static F
.
For stateless closures, this works as is, otherwise (stateful closures) the caller would be needed to, for instance, Box::leak(Box::new(move || ...))
(you can take a f: F
rather than : &'static F
and do the Box::leak
ing yourself inside your exposed function's body, but although ergonomic, that will prevent you from sharing the callback in other places).
If you know no parallel accesses will happen / the callback does not need to be thread-safe, you can drop the Sync
bound.
-
If on top of that, you know no concurrent calls will ever happen (e.g., re-entrant code), you can use FnMut() + Send
instead (with an &'static mut
for the reference case).
- If, on top of the two previous things, you know these sequential calls will happen on the same thread where the registration happened, you can drop the
Send
bound.
Another approach, since the registering callback seems to take a render_context
, is to try and tie the callback to the lifetime of that render context (at least that of the rusty wrapper): take an Arc<impl 'static + Fn() + Send + Sync>
(or an Rc<impl 'static + Fn()>
if thread-bound / single-threaded), downgrade it to a Weak
which is the raw pointer that gets used as the ctx
, and have your rusty wrapper of the context
hold the owning {A,}Rc
on its own: as long as the context lives, so will the closure, and when the rusty context dies, that {A,}Rc
is dropped, invalidating the C callback in a controlled manner (when that C callback tries to upgrade the ctx
, if it fails, it can simply no-op (or, when available, return some error code)). This reduces the "leakage" to the some heap-allocated memory big enough to hold two counters, and the shallow no-indirection size of the closure captures (if the shallow size is big, then you can {A,}Rc<Box<F>>
, so that that shallow size is just that of a pointer, leading to leaked Weak
leaking just 3 usizes
worth of memory, so 24 bytes). So, for instance, if the closure captured a file handle or a socket handle, those would be properly closed.
TL,DR
As you can see, there are no pretty solutions, but that's because the C API is already quite flawed to begin with (which is, sadly, often the case).