Forward Interop

Callable items specific to exposing Rust to other languages (e.g., C#) via the #[ffi] attribute. Most type, pattern and Wire constructs are supported, for details see the respective #[ffi] documentation.

Functions

Regular freestanding functions are supported, accepting or returning most types:

#[ffi]
pub fn primitive_void() {}

#[ffi]
pub fn primitive_f64(x: f64) -> f64 { -x }

#[ffi]
pub fn ref_mutable(x: &mut i64) -> &mut i64 { *x = -*x; x }

#[ffi]
pub fn generic(_x: Generic<u32>, _y: Generic<u8, 5>) { }

#[ffi]
pub fn wire(x: Wire<String>) {}

Services

Services turn Rust impl blocks into class-like objects with constructors, methods, and automatic cleanup.

Basic Service

#[ffi(service)]
pub struct ServiceBasic {}

#[ffi]
impl ServiceBasic {
    pub fn create() -> ffi::Result<Self, Error> {
        ffi::Ok(Self {})
    }
}

Methods & Self

Methods take &self or &mut self. Return types can be primitives, structs, enums, ffi::Result, ffi::String, ffi::Slice, or ffi::Vec.

#[ffi]
impl ServiceMethods {
    pub fn create() -> ffi::Result<Self, Error> { ffi::Ok(Self::default()) }
    pub fn add(&self, x: u32, y: u32) -> u32 { x + y }
    pub fn set(&mut self, x: u32) { self.value = x; }
}

Note that methods taking &mut self must still honor Rust's aliasing guarantee. For example, you must not call these methods in parallel from two different threads.

Multiple Constructors

Any method returning Self (or Result<Self, E>) is treated as a constructor.

#[ffi]
impl ServiceMultipleCtors {
    pub fn new_with(some_value: u32) -> ffi::Result<Self, Error> { /* ... */ }
    pub fn new_without() -> ffi::Result<Self, Error> { /* ... */ }
    pub fn new_with_string(_: ffi::CStrPtr) -> ffi::Result<Self, Error> { /* ... */ }
    pub fn new_failing(_: u8) -> ffi::Result<Self, Error> { ffi::Err(Error::Fail) }
}

Service Dependencies

Constructors and methods can take references to other services.

#[ffi]
impl ServiceDependent {
    pub fn from_main(main: &ServiceMain) -> ffi::Result<Self, Error> {
        ffi::Ok(Self { val: main.val })
    }
    pub fn pass_main(&self, _: &ServiceMain) {}
}

Async Services

Add #[derive(AsyncRuntime)] and a runtime field, or derive the trait manually. Async methods take Async<Self> instead of &self, which also provides special access to the thread-local runtime context in runtimes that support it.

#[ffi(service)]
#[derive(AsyncRuntime)]
pub struct ServiceAsyncBasic {
    runtime: Tokio,
}

#[ffi]
impl ServiceAsyncBasic {
    pub fn create() -> ffi::Result<Self, Error> {
        result_to_ffi(|| Ok(Self { runtime: Tokio::new() }))
    }

    pub async fn call(_: Async<Self>) -> ffi::Result<(), Error> {
        ffi::Ok(())
    }
}

API Guard

We highly recommend you also utilize API guards. When registered via guard!, they emit a version check so the foreign side can verify it was generated against the same API.

#[ffi]
pub fn api_guard() -> Version {
    crate::inventory().into()
}

Inventory Registration

All items must be registered in a RustInventory. The inventory uses a builder pattern with dedicated macros:

pub fn inventory() -> RustInventory {
    RustInventory::new()
        .register(builtins_string!())
        .register(builtins_vec!(u8))
        .register(builtins_vec!(ffi::String))
        .register(guard!(api_guard))
        .register(function!(my_function))
        .register(constant!(MY_CONST))
        .register(extra_type!(Generic<f32>))
        .register(service!(MyService))
        .validate()
}

In most cases only functions and services must be registered, types are inferred.

MacroPurpose
function!()Register a single #[ffi] function
constant!()Register a #[ffi] constant
service!()Register a #[ffi(service)] type + its impl block
extra_type!()Register a concrete generic specialisation
builtins_string!()Register ffi::String support functions
builtins_vec!(T)Register ffi::Vec<T> support functions
builtins_wire!()Register Wire<T> support functions