How to deal with lifetime when need to expose through FFI

I am using specs that require some lifetime types to keep reference of the dispatcher. (https://docs.rs/specs/0.15.1/specs/struct.Dispatcher.html)

I have some context struct that hold things together.

pub struct Context<'a, 'b> {
    pub world: World,
    pub dispatcher: Dispatcher<'a, 'b>,
    ...
}

I am trying to use it through FFI by returning a handler pointer to the host language:

#[no_mangle]
pub extern "C" fn init() -> *mut Context {
    let mut context = Context::new(value);
    Box::into_raw(Box::new(context))
}

The the host language send data providing the handler

fn from_ptr(ptr: *mut Context) -> &'c mut Context {
    assert!(!ptr.is_null());
    unsafe {
        &mut *ptr
    }
}

#[no_mangle]
pub extern fn update(ctx_ptr: *mut Context, delta: u32) -> u32 {
    let ctx = from_ptr(ctx_ptr);
    ctx.update(delta)
    0
}

From what I understand, there is no black magic to represent the lifecycle when casting it from raw pointers.

Any suggestion how to deal with it?

For now what I found:

A) Pray for success of encapsulation of private lifetimes (https://internals.rust-lang.org/t/pre-rfc-encapsulating-private-lifetimes/7500).

Too much hope?

B) Get rid of Dispatcher reference that require the lifecycles by dynamically creating each tick.

It will works, but this means whatever I use from now one can not have lifecycles.

Too much optimism?

C) Keep a unsafe global variable with Context and just share some KEY through FFI.

I am not complete even sure if it works.

Too much lack of knowledge?

The best thing then is to use an opaque type pattern:

#[repr(C)]
pub
struct FfiContext {
    _priv: [u8; 0],
    // Optional; to get the `Send` & `Sync` bounds right.
    _send_sync: ::core::marker::PhantomData<Context<'static, 'static>>,
);

#[no_mangle] pub extern "C"
fn init ()
  -> *mut FfiContext
{
    Box::into_raw(Box::new(Context::new()))
        .cast()
}

unsafe
fn from_ptr<'_0, '_1, '_2> (ptr: *mut FfiContext)
  -> &'_0 mut Context<'_1, '_2>
{
    if ptr.is_null() {
        eprintln!("Fatal error, got NULL `Context` pointer");
        ::std::process::abort();
    }
    &mut *(ptr.cast())
}

#[no_mangle] pub unsafe extern "C"
fn update (p: *mut FfiContext)
  -> u32
{
    let ctx: &'_ mut Context<'_, '_> = from_ptr(p);
    ctx.update(delta);
    0
}

#[no_mangle] pub unsafe extern "C"
fn free_context (p: *mut FfiContext)
{
    drop::<Box<Context<'_, '_>>>(Box::from_raw(from_ptr(p)))
}
2 Likes

Amazing, it is working.

I am surprised that you can cast into a struct with lifetimes. Looks like I need go back to read some documentation.

Thanks!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.