I have some (highly unsafe) code that needs to store, and later invoke, a callback function (an FnOnce closure). The closure is stored as a part of a larger structure, not boxed on the heap.
The code that actually needs to invoke the function only has a type-erased dyn pointer to it, namely *mut dyn FnOnce().
My question is, how do I actually invoke the function?
Naturally, there'd be a lot of unsafe; in particular the caller would have to arrange for the closure to not get dropped (again) after the call, since the call itself consumes it. This is not an issue; I can do as much unsafe trickery as is necessary.
My issue is, I just don't see any way to actually invoke the function that is not a gross hack (e.g. extracting the vtable from the fat pointer and assuming it has a particular layout) that is likely to break on architectures other than the one I'm testing and on different versions of Rust.
One way I did come up with is to create a Box<dyn FnOnce(), NoopAlloc> from the raw pointer; then the boxed dyn FnOnce() can be easily invoked. But support for custom allocators is unstable, so I cannot do that.
Another way I came up with is to wrap the closure into an FnMut() while it's still Sized, and then only call that new closure once:
let fn_once = Some(f);
let fn_mut = move || if let Some(f) = fn_once.take() { f(); }
let fn_mut = &mut fn_mut as *mut dyn FnMut();
But this doesn't work in my use case for other reasons — namely, I cannot name the new closure's type without impl Trait aliases, but I have to store it as a field in a structure.
Is there perhaps some obvious way that I'm missing?
Seems like std uses the unstable unsized_locals feature internally to do its Box<dyn FnOnce(…) -> …>: FnOnce(…) -> … implementation. You could use your own Callback helper trait that has some unsafe call(&mut self) and uses std::ptr::read internally.
use std::mem::ManuallyDrop;
struct S<F: FnOnce()> {
closure: F,
other: i32,
stuff: i32,
}
impl<F: FnOnce() + 'static> S<F> {
pub fn new(closure: F) -> Self {
S {
closure,
other: 35,
stuff: 42,
}
}
}
unsafe fn some_other_code(callback: *mut dyn Callback) {
(*callback).call();
}
fn main() {
let s = "abc".to_string();
// manually drop to avoid double free
let mut s = ManuallyDrop::new(S::new(move || println!("call me once - {}", s)));
unsafe { some_other_code(&mut s.closure) };
}
////////////////////////////////////////////////////////////////////////////////
trait Callback: FnOnce() {
unsafe fn call(&mut self);
}
impl<F: FnOnce()> Callback for F {
unsafe fn call(&mut self) {
std::ptr::read(self)()
}
}
Yes; but the vtable pointer you get out of this is vtable: *mut (), so then you'd need to assume the vtable has a particular layout, and that will likely break.
This post (ru) has some fun examples of what can be done this way; but this is all highly unstable and likely to break; so I cannot use that.