Deallocating a closure passed into C with Box::into_raw

I'm working with a callback-based C api. I'm using this cool pattern I found on the internet to pass a boxed closure into C (I've tweaked the code to make the example simpler):

unsafe extern "C" fn generic_callback<F>(data: *mut c_void, userdata: *mut c_void)
where
    F: FnMut(*mut c_void) + 'static,
{
    let closure = &mut *(userdata as *mut F);
    closure(data);
}


fn add_callback<F>(&mut self, closure: F)
where
    F: FnMut(*mut c_void) + 'static,
{
    let p = Box::into_raw(Box::new(closure));
    let cb = generic_callback::<F>;
    unsafe {
        c_register_cb(cb,  std::ptr::addr_of_mut!(*p) as *mut c_void);
    }
}

Later on, I'd like to free the closure. Once I get the pointer back from C (let's ignore how that works because it's not important), I know I'm supposed to use Box::from_raw():

let _closure = Box::from_raw(p as *mut dyn FnMut(*mut c_void) as &mut _);
// _closure goes out of scope and is dropped

But this doesn't compile, because the T in Box<T> isn't known. Is there a way to generically drop a box pointer without instantiating it fully?

1 Like

Not when all you have is the pointer. You don't know how large the pointed-to object is. How could you know how much to deallocate?

Isn't a Box a pointer with some extra info? How else would rust drop a Box<dyn Foo> in the normal case?

Box<dyn Foo> and &dyn Foo and *mut dyn Foo, etc, are wide pointers which consist of a pointer to the type-erased struct and a pointer to a static vtable. The vtable in turn contains the size of the type-erased struct, a pointer to the destructor, and other data. That's the "extra data" in the case of Box<dyn Foo>.

But you're not dealing with any of those, and even if you were, you would be throwing away the vtable pointer when you cast to a *mut c_void.

(There may be some additional confusion around what dyn Trait is. In particular, it's a concrete type but not a supertype or anything like that. A Box<F> where F: FnMut(*mut c_void) /* + Sized */ is not the same thing as a Box<dyn FnMut(*mut c_void)>. A Box<F> for any Sized F stores only a pointer to the data.)


An actually useful answer to your question will probably involve storing a type-erased wide pointer, but I'm still mulling the specifics over.

1 Like

Ah, I was definitely confusing Box<dyn Foo> with Box<F> where F: Sized. Thank you for clarifying.

You can get a narrow pointer by using Box<Box<dyn FnMut(*mut c_void)>>::into_raw() at the cost of an extra allocation and level of indirection.

2 Likes

Actually, maybe I could restructure it to actually use trait objects? I just ran across the thin_trait_object crate, which seems like it would help.

And in particular, Box<Box<dyn FnMut(*mut c_void)>>::from_raw() is a valid function to call for deallocation.

1 Like

Some sort of thin pointer (double boxing or a crate, if it's sound) is probably a better approach than I was mulling for this use case.