Invoke *mut dyn FnOnce()

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?

Here's a (much simplified) code snippet:

struct S<F: FnOnce()> {
    closure: F,
    other: i32,
    stuff: i32,
}

impl<F: FnOnce()> S<F> {
    pub fn new(closure: F) -> Self {
        S {
            closure,
            other: 35,
            stuff: 42,
        }
    }
}

fn some_other_code(callback: *mut dyn FnOnce()) {
    // callback();
    // ^^^^^^^^^^^ this does not work :(
}

fn main() {
    let mut s = S::new(|| println!("call me once"));
    some_other_code(&mut s.closure);
}

Actually, there is a uniform way to get the vtable and data: std::raw::TraitObject - Rust (rust-lang.org). But it's unstable too :sob:

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)()
    }
}
2 Likes

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.

This is brilliant, thank you!

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.