Passing impl trait reference across FFI boundary

Hi, I have some code like

trait Foo {
  type A;
  type B;
  type C;
  type D;

  fn foo();
}

fn called_by_c(context: Context) {
  // How do I access foo here?
}

fn bar(foo: &mut impl Foo) {
  let context: Context = todo!();

  unsafe { c_function(context) };
}

I need to store foo in place where it can be accessed later by some Rust code that is invoked by C code (this code will always be executed within the scope of foo ). Unfortunately I do not control the signature of bar, so I have to take &mut impl Foo[1]. I do however have complete control of Context (it's passed through C back to me unchanged).

I initially thought to store Option<*mut impl Foo> in Context, but I can't use impl Foo in a field. Using a trait object or generic parameters is not ideal because of the large number of associated types (ideally they would be erased so that they do not infect the rest of the codebase). What are my options here? Given the constraints, I'm wondering if I have to resort to some black magic...


  1. The actual trait in use is piet::RenderContext ↩︎

You could transmute it into *mut () and back?

you could also use a dyn Trait object.

trait Foo {
  type A;
  type B;
  type C;
  type D;

  fn foo();
}

// If you pass `Context` through C as more than a `void *`,
// you need to define a shared representation.
// (If you pass `Context` by value through FFI, you need this #[repr].)
#[repr(C)]
struct Context {
    ptr: *mut (),
    thunk: unsafe fn(*mut ()),
}

fn whatever_called_by_c_actually_does<F: Foo>(foo: &mut F) {
    // do something with foo
}

fn called_by_c(context: Context) {
    unsafe { (context.thunk)(context.ptr) }
}

fn bar<F: Foo>(foo: &mut F) {
    let context = Context {
        ptr: foo as *mut F as *mut (),
        // The thunk is monomorphized for each type that bar() is called with
        thunk: |ptr| {
            let foo = unsafe { ptr.cast::<F>().as_mut().unwrap() };
            whatever_called_by_c_actually_does(foo)
        },
    };
    unsafe { c_function(context) }
}

IIRC this is basically how vtables/dyn Trait work. It has a few differences though:

  • dyn Foo would require specifiying associated types, while this method allows the inner function to work with any associated types (but not pass them from whatever_called_by_c_actually_does up through the call stack. This isn’t a real limitation because C wouldn't know about the associated types anyway.)
  • dyn Foo trait objects have extra overhead for passing more information (size, alignment, another thunk for dropping the erased type)

If you really need bar to take exactly a &mut impl Foo, you can declare my bar function as bar_inner and have bar call it. There should be no difference with making bar take a &mut F where F: Foo, though[1].


  1. unless your provided bar function in OP has other turbofishable type arguments, and callers use turbofished bar::<T, U, V>, so the new F argument changes the required turbofish. but that's very unlikely. ↩︎

This is really clever, thank you!

I ended up adding an additional enum parameter to the thunk so that I can invoke different functions (with different arguments) based on how called_by_c is invoked.

I can't implement these as inherent methods anymore, but I think that's OK, I think I can just pass whatever fields I need by reference in the enum.