Custom placed FnOnce: how to call properly?


#1

Hi,

I’m toying with coroutines, related ASM and custom placement.
What I basically have at some point is some extern function which receives address of some service struct, manually placed somewhere in RAM. That extern function must invoke that struct as a () -> () function and then drop it properly, BUT without deallocating its memory, moving it somewhere etc.

First I thought about just placing &FnOnce() + Drop fat pointer near struct, and reference it. But it’s not possible because FnOnce should own and move its context. That struct will also definitely contain FnOnce()+Send+'static, which must be also called properly.

The only idea I have is to read &FnOnce at provided address and then unsafely (*ref)().
Any other ideas?

Thanks


#2

Maybe something like this?

use std::ptr;
use std::mem;
unsafe fn call_once_ptr<T: FnOnce()>(p: *mut ()) {
     ptr::read(p as *mut T)();
}
struct OnceCaller {
    ptr: *mut (),
    f: unsafe fn(*mut ())
}
fn get_once_caller<T: FnOnce()>(ptr: *mut T) -> OnceCaller {
    OnceCaller { ptr: ptr as *mut (), f: call_once_ptr::<T> }
}
unsafe fn do_call_once(o: OnceCaller) {
    (o.f)(o.ptr)
}
fn main() {
    let mut once = || println!("asdf");
    let c = get_once_caller(&mut once);
    unsafe { do_call_once(c); }
    mem::forget(once);
}

You can probably make it a bit nicer, but the basic idea is to manually build a “trait reference” of sorts.


#3

Won’t it be moved? Or Rust is smart enough to not move data unless necessary?


#4

Oh. You’re right, it’ll move the data. There isn’t any way in Rust currently to write ptr::read(p as *mut T)(); in a way that avoids the move. Of course, depending on the calling convention you’re using, there might be a hard requirement that the data gets moved anyway.

If you really need to avoid copying, you shouldn’t use FnOnce; just implement the relevant convention by hand.