Introducing Rusty-bind
rusty-bind is an open source project intended to provide a unified tool for generating bindings from Rust code to popular programming languages and runtime environments. rusty-bind is being developed by the Wildland development team and has been made possible thanks to funding from Golem Foundation.
Purpose
rusty-bind aims at being a target-agnostic binding generator. By this we mean that there is consistent set of rules you can use when designing Rust APIs, for which binding code can be generated for multiple OS targets and programming languages. The goal is that generated APIs should match the generator input as much as possible and, at the same time, be consistent across all the target languages.
Reason
While working on Wildland’s cross-platform core we’ve found that there are no tools that could be used satisfactorily to generate bindings allowing the non-trivial use of Rust APIs in heterogeneous projects, where a cross platform library is expected to be consumed by different subprojects written in different languages.
While there are several tools that can be used to provide interop between Rust and other languages, they come with multiple limitations, namely:
- each focuses on a single target language, so projects that need to use Rust code in Java and Swift at the same time, have to rely on different tools
- most differ when it comes to constraints put on the FFI layer (i.e. using traits, callbacks or tuples in public APIs might be allowed by one tool, but not supported by the other)
- some offer poor developer experience by generating APIs that, even though callable from the target language, are very low level and thus error-prone
These limitations are manageable in the context of a single target language, but in the context of writing a Rust library that could be reused on different platforms, we found them to be a deal-breaker. While rusty-bind is in its very early stages we believe that it will allow us to mitigate at least some of above-mentioned problems.
Usage
rusty-bind is a rapidly changing project, so if you are interested in trying it out, be sure to check it’s documentation for up to date status and usage information.
To use rusty-bind you need to annotate the code module intended for export with a #[binding_wrapper]
macro and provide a build file to execute a parser.
Usage example
- Add needed dependencies to the
Cargo.toml
file:
[lib]
crate-type = ["staticlib", "lib"]
# [...]
[dependencies]
rusty-bind = { version = "0.1.0" }
# [...]
[build-dependencies]
rusty-bind-build = { version = "0.1.0" }
- Prepare a
build.rs
file:
fn main() {
use rusty_bind_build::parse_ffi_module;
parse_ffi_module("src/ffi/mod.rs", "./_generated_ffi_code/").unwrap();
println!("cargo:rerun-if-changed=src/ffi/mod.rs");
}
- Prepare an FFI module to expose your APIs, i.e.:
//
// File: src/ffi/mod.rs
//
use rusty_bind::binding_wrapper;
use std::sync::{Arc, Mutex};
// Define Error type and `()` type.
#[derive(Clone, Debug)]
#[repr(C)]
pub enum ErrorType {
Error
}
pub trait ExceptionTrait {
fn reason(&self) -> String;
}
impl ExceptionTrait for ErrorType {
fn reason(&self) -> String {
match self {
ErrorType::Error => {
"Error Message".to_owned()
}
}
}
}
pub trait SomeTrait: std::fmt::Debug {
fn some_trait_method(&self);
}
#[derive(Clone, Debug)]
pub struct Foo(u32);
impl SomeTrait for Foo {
fn some_trait_method(&self) {
}
}
#[derive(Clone, Debug)]
pub struct CustomType(u32);
impl CustomType {
pub fn return_result_with_dynamic_type(&self) -> Result<Arc<Mutex<dyn SomeTrait>>, ErrorType> {
Ok(Arc::new(Mutex::new(Foo(10u32))))
}
pub fn return_another_custom_type(&self) -> AnotherCustomType {
AnotherCustomType(20u64)
}
}
#[derive(Clone, Debug)]
pub struct AnotherCustomType(u64);
impl AnotherCustomType {
pub fn take_primitive_type_and_return_primitive_type(&self, a: u32) -> String {
format!("Result: {a}")
}
}
#[binding_wrapper]
mod ffi {
enum ErrorType {
Error
}
extern "ExceptionTrait" {
fn reason(&self) -> String;
}
extern "Rust" {
fn return_result_with_dynamic_type(self: &CustomType) -> Result<Arc<Mutex<dyn SomeTrait>>, ErrorType>;
fn return_another_custom_type(self: &CustomType) -> AnotherCustomType;
fn take_primitive_type_and_return_primitive_type(self: &AnotherCustomType, a: u32) -> String;
fn some_trait_method(self: &Arc<Mutex<dyn SomeTrait>>);
}
}
- Run
cargo build
This will generate a static library and glue code for supported target languages.
Architecture
rusty-bind achieves its goals by having flexible architecture, where a single code parser and type library are used together with language-specific code generators. This way, supporting another target language is possible by writing a generator for it. Alternatively, it’s possible to use C++ target and use SWIG to generate bindings from C++ to the target language. This idea is illustrated on the diagram below.
Status
rusty-bind is in the early stages of its development and is not yet ready for use in production applications. The generated binding interfaces are not yet stable and they are expected to change as the project matures. The development is primarily driven by the needs of the Wildland project. This means that we focus on language features for and target platforms that we currently use in Wildland.
Supported features include referencing structures, calling functions, support for enums, optionals, results and traits. Passing native objects implementing Rust traits as function arguments should work, but it is the responsibility of the caller to ensure that passed object will be valid during the time when Rust code uses its reference.
Our current focus is on improving swift and javascript code generation. Early work has also been done on the swig interface generator.
GPL3 License
Golem Foundation is committed to providing and improving open source software and to making sure it remains free for all its users in the future. This means that, like other software from Golem Foundation, rusty-bind is released under GPL3 license. If you find what we do valuable and would like to contribute to the project, please read how you can help rusty-bind grow.