Procedural Macros: Attributes and Derives
UniFFI allows you to define your function signatures and type definitions directly in your Rust code, avoiding the need to duplicate them in a UDL file and so avoiding the possibility for the two to get out of sync. This mechanism is based on Procedural Macros (proc-macros), specifically the attribute and derive macros.
You can have this mechanism extract some kinds of definitions out of your Rust code, in addition to what is declared in the UDL file. However, you have to make sure that the UDL file is still valid on its own: All types referenced in fields, parameter and return types in UDL must also be declared in UDL.
Further, using this capability probably means you still need to refer to the UDL documentation, because at this time, that documentation tends to conflate the UniFFI type model and the description of how foreign bindings use that type model. For example, the documentation for a UDL interface describes both how it is defined in UDL and how Swift and Kotlin might use that interface. The latter is relevant even if you define the interface using proc-macros instead of in UDL.
⚠ Warning ⚠ This facility is relatively new, so things may change often. However, this remains true for all of UniFFI, so proceed with caution and the knowledge that things may break in the future.
Build workflow
Be sure to use library mode when using UniFFI proc-macros (See the Foreign language bindings docs for more info).
If your crate's API is declared using only proc-macros and not UDL files, call the uniffi::setup_scaffolding
macro at the top of your source code:
uniffi::setup_scaffolding!();
⚠ Warning ⚠ Do not call both uniffi::setup_scaffolding!()
and uniffi::include_scaffolding!!()
in the same crate.
The #[uniffi::export]
attribute
The most important proc-macro is the export
attribute. It can be used on functions, impl
blocks, and trait
definitions to make UniFFI aware of them.
#[uniffi::export]
fn hello_ffi() {
println!("Hello from Rust!");
}
For more details: * Records * Enums * Interfaces * Functions, constructors, methods * Errors
The uniffi::Object
derive to extend interfaces defined in UDL
This derive can be used to replace an interface
definition in UDL. Every object type must have
either an interface
definition in UDL or use this derive macro. However, #[uniffi::export]
can be used on an impl block for an object type regardless of whether this derive is used. You can
also mix and match, and define some method of an object via proc-macro while falling back to UDL
for methods that are not supported by #[uniffi::export]
yet; just make sure to use separate
impl
blocks:
// UDL file
interface Foo {
void method_a();
};
// Rust file
// Not deriving uniffi::Object since it is defined in UDL
struct Foo {
// ...
}
// Implementation of the method defined in UDL
impl Foo {
fn method_a(&self) {
// ...
}
}
// Another impl block with an additional method
#[uniffi::export]
impl Foo {
fn method_b(&self) {
// ...
}
}
The uniffi::custom_type
and uniffi::custom_newtype
macros
See the general documentation for Custom Types, which apply equally to proc-macros as to UDL.
The #[uniffi::export(callback_interface)]
attribute
#[uniffi::export(callback_interface)]
can be used to export a callback interface definition.
This allows the foreign bindings to implement the interface and pass an instance to the Rust code.
#[uniffi::export(callback_interface)]
pub trait Person {
fn name() -> String;
fn age() -> u32;
}
// Corresponding UDL:
// callback interface Person {
// string name();
// u32 age();
// }
Conditional compilation
uniffi::constructor|method]
will work if wrapped with cfg_attr
attribute:
#[cfg_attr(feature = "foo", uniffi::constructor)]
Mixing UDL and proc-macros
If you use both UDL and proc-macro generation, then your crate name must match the namespace in your UDL file. This restriction will be lifted in the future.