Overview of Diplomat's supported target languages
mainDiplomat generates high-level bindings from Rust APIs to several languages, including:
- C
- C++
- Dart
- Javascript/Typescript
- .NET (C#)
- Kotlin (using JNA)
- Python (using nanobind)
repository·main·Indexed 21 days ago
https://github.com/rust-diplomat/diplomatA Rust tool that automates the generation of high-level FFI (Foreign Function Interface) bindings from Rust APIs for languages including C, C++, Dart, JavaScript/TypeScript, .NET (C#), Kotlin, and Python. It provides macros and attributes like #[diplomat::bridge], #[diplomat::abi_rename], and #[diplomat::attr] to customize code generation, manage ABI stability, and implement language-specific features such as getters, setters, and operator overloading.
Diplomat generates high-level bindings from Rust APIs to several languages, including:
This project serves as a proof of concept for the tool/src/demo_gen tool. It demonstrates how to generate a web-based demonstration (using pure HTML and JavaScript) from a library's API.
In this specific example, the implementation assumes the project already uses JavaScript. The example is structured by copying the API definitions from example/lib/js/api to treat them as a unified package for the demonstration.
demo_gen is a Diplomat Backend that leverages Diplomat's JavaScript backend to automatically generate usage examples for your library's FFI (Foreign Function Interface) bindings in JavaScript.
For a complex library like ICU4X, instead of requiring users to compile packages to test functionality, demo_gen can read functions (e.g., FixedDecimalFormatter.formatDecimal) and automatically generate an HTML page with the necessary inputs (like a number field and locale selector) and the underlying logic to demonstrate the function in action.
At the lowest level, WebAssembly (Wasm) only supports two parameter and return types: i32 and i64. The JS-Wasm interface maps Number to i32 and BigInt to i64.
This means all other types are converted to these integers during FFI:
Warning: Current Diplomat code may have issues with large u32 values (which may turn into negative numbers across FFI) and u64 values (which require BigInt conversion).
Diplomat is designed to bridge Rust projects to multiple target languages by using a central, stable C API as an intermediary. Instead of manually writing wrappers for every language (e.g., C++, Java, JavaScript), developers define their public FFI API using tagged "bridge blocks" in Rust.
Key architectural principles include:
cxx-style tagged bridge blocks to define the API surface.Diplomat allows you to selectively disable or enable bindings based on a list of features. These features are independent of Cargo features. You can use the #[diplomat::attr(not(feature=some_feature), disable)] attribute to ensure a module or item only appears for backends where some_feature is enabled.
To enable these features globally across all backends, use the features_enabled configuration key.
#[diplomat::attr(not(feature=some_feature), disable)]
mod ffi {}
#[diplomat::config(features_enabled=["this_feature", "some_feature"])]
struct Config;pub keyword for functions. Any function within a #[diplomat::bridge] module that does not have the pub modifier will not have bindings generated for it.In Diplomat, you can use the standard Rust Option<T> for function parameters and return values. Diplomat automatically converts these into the idiomatic equivalent for the target language across the FFI boundary.
Supported Types for Option<T>:
Box<OpaqueType> or &OpaqueType>)Language Mapping Examples:
Option<Box<T>> becomes std::optional<std::unique_ptr<T>>. Primitives like Option<u8> become std::optional<uint8_t>.Option<T> returns a potentially-null object or an integer-or-null.#[diplomat::bridge]
mod ffi {
#[diplomat::opaque]
pub struct Thingy;
impl Thingy {
pub fn maybe_create() -> Option<Box<Thingy>> {
Some(Box::new(Thingy))
}
pub fn increment_option(x: Option<u8>) -> Option<u8> {
x.map(|inner| inner + 1)
}
}
}Diplomat ensures memory safety in .NET through several mechanisms:
RustHandle<T>._edges array that roots the source object, preventing the .NET GC from collecting the source while a reference is still live.Dispose(), a finalizer serves as a last-resort cleanup path for owned handles. Native calls use GC.KeepAlive(this) to prevent premature finalization during P/Invoke execution.demo_gen backend supports all standard #[diplomat::attr] attributes. A common use case is using the disable attribute to prevent specific functions from appearing in the generated demonstrations. Because demo_gen is built on the JS backend, any methods disabled for JS will also be disabled in the demo_gen output.When you want to expose a Rust type over FFI without revealing its internal fields, use an opaque type. Opaque types can contain any data, but they can only be passed over FFI behind pointers (they cannot be passed on the stack). The consumer (e.g., C++, JS) can only interact with the type by calling explicitly defined methods on it.
To create an opaque type, wrap the original Rust type in a new struct within a #[diplomat::bridge] block and annotate it with #[diplomat::opaque].
struct Person {
name: String,
age: u8,
}
impl Person {
pub fn new(name: String, age: u8) -> Self {
Self { name, age }
}
pub fn get_age(&self) -> u8 {
self.age
}
}
#[diplomat::bridge]
mod ffi {
use super::Person as RustPerson;
#[diplomat::opaque]
pub struct Person(RustPerson);
impl Person {
pub fn create(name: String, age: u8) -> Box<Self> {
Box::new(Person(RustPerson::new(name, age)))
}
pub fn get_age(&self) -> u8 {
self.0.get_age()
}
}
}For each Rust trait, Diplomat generates a Trait struct and a VTable struct. The Trait struct contains a pointer to the data and a pointer to the VTable. The VTable includes the destructor, SIZE, and ALIGNMENT of the data, along with function pointers for the trait methods.
typedef struct DiplomatTraitStruct_TraitName {
void *data;
TraitName_VTable vtable;
} DiplomatTraitStruct_TraitName;
typedef struct TraitName_VTable {
void (*destructor)(const void*);
size_t SIZE; size_t ALIGNMENT;
/* ... */
} TraitName_VTable;