Hello Rust experts!
After hand-rolling some Rust-OCaml bindings I started to think about how could I possibly automate this process (if not completely, at least partially by providing some not-really-working boilerplate that a human could fix).
I'll be honest, I'm not entirely sure this is at all possible But right now I'm thinking about traits and how can I work with them in generated bindings.
Some background on how Rust objects are exposed to OCaml. In most of the cases they are exposed as opaque objects which have associated functions to work with them. OCaml FFI interface is quite alike with Python one, there are C API functions that manipulate the state of managed objects (interact with GC, etc). From Rust side the FFI interface is a bunch of foreign "C" functions that OCaml calls.
So opaque value is created from Rust side like this (code from ocaml-interop):
/// Allocate a `DynBox` for a value of type `A`.
pub fn alloc_box<A: 'static>(cr: &mut OCamlRuntime, data: A) -> OCaml<DynBox<A>> {
let oval;
// A fatter Box, points to data then to vtable
type B = Pin<Box<dyn Any>>;
unsafe {
oval = ocaml_sys::caml_alloc_custom(&BOX_OPS_DYN_DROP, mem::size_of::<B>(), 0, 1);
let box_ptr = ocaml_sys::field(oval, 1) as *mut B;
std::ptr::write(box_ptr, Box::pin(data));
}
unsafe { OCaml::new(cr, oval) }
}
DynBox<T>
is defined as follows:
/// [`OCaml`]`<OCamlFloatArray<T>>` is a reference to an OCaml `floatarray`
/// which is an array containing `float`s in an unboxed form.
pub struct OCamlFloatArray {}
/// `OCaml<DynBox<T>>` is for passing a value of type `T` to OCaml
///
/// To box a Rust value, use [`OCaml::box_value`][crate::OCaml::box_value].
///
/// **Experimental**
pub struct DynBox<A> {
_marker: PhantomData<A>,
}
Important aspect of OCaml GC is that it's a moving GC, and that custom allocated block (via ocaml_sys::caml_alloc_custom
) can actually be moved to another memory location during GC phase. That Pin<Box<dyn Any>>;
is trying to encode that assumption into Rust (based on my understanding).
This works great, you export your opaque type to OCaml, along with some C functions that can recover it back from OCaml and call some Rust methods on it.
Now back to the traits problem... When your Rust type implements a trait, it gains some methods. Right now I just write corresponding wrappers for those methods for that type, but overall it would be nice to get rid of that boilerplate by defining trait binding code in one place, and apply it to all objects which implement that trait. Another part of the problem is that sometimes Rust API expect "anything satisfying a trait", and I'm a bit lost on how to encode that into Rust/OCaml interop.
That dyn Any
inside the pinned box is nice, it allows to catch cases when other object is passed from OCaml side, it panics with somewhat actionable error. But it does not work when I have DynBox<A>
and want to pass it to somewhere where DynBox<dyn MyTrait>
is expected. Based on my internet search results, Rust does not have enough information at runtime to figure out if dyn Any
implements dyn MyTrait
, it only has type id which can guarantee safe downcast back from Any
to concrete type.
Ideally I want to write a bunch of binding C functions for trait methods, and then have it be applicable for all type instances which implement that trait.
Is it possible to have some registry of type id => dyn trait convertor functions? Probably it could be safely populated with some macro by hand, and in my trait-binding functions I just pass my dyn Any
and dyn Trait
to some lookup function, which will get corresponding converter function, idexed by type ids of corresponding type behind dyn Any
and id of dyn Trait
? If dyn Trait
does not really have type id, probably stringified fully-qualified name of Trait should be good enough.
Any pointers are greatly appreciated.