BoltFFI
A high-performance multi-language bindings generator for Rust. Up to 1,000x faster than UniFFI. Up to 450x faster than wasm-bindgen.
Quick links: User Guide | Tutorial | Getting Started
Performance
vs UniFFI (Swift/Kotlin)
| Benchmark | BoltFFI | UniFFI | Speedup |
|---|---|---|---|
| noop | <1 ns | 1,416 ns | >1000x |
| echo_i32 | <1 ns | 1,416 ns | >1000x |
| counter_increment (1k calls) | 1,083 ns | 1,388,895 ns | 1,282x |
| generate_locations (1k structs) | 4,167 ns | 1,276,333 ns | 306x |
| generate_locations (10k structs) | 62,542 ns | 12,817,000 ns | 205x |
vs wasm-bindgen (WASM)
| Benchmark | BoltFFI | wasm-bindgen | Speedup |
|---|---|---|---|
| 1k particles | 29,886 ns | 13,532,530 ns | 453x |
| 100 particles | 3,117 ns | 748,287 ns | 240x |
| 1k locations | 21,931 ns | 4,037,879 ns | 184x |
| 1k trades | 42,015 ns | 5,781,767 ns | 138x |
| 100 locations | 2,199 ns | 283,753 ns | 129x |
Full benchmark code: benchmarks
Why BoltFFI?
Serialization-based FFI is slow. UniFFI serializes every value to a byte buffer. wasm-bindgen materializes every struct as a JavaScript object. That overhead shows up even when you're making tens or hundreds of FFI calls per second.
BoltFFI uses zero-copy where possible. Primitives pass as raw values. Structs with primitive fields pass as pointers to memory both sides can read directly. WASM uses a wire buffer format that avoids per-field allocation. Only strings and collections go through encoding.
What it does
Mark your Rust types with #[data] and functions with #[export]:
use *;
Run boltffi pack:
# Produces: ./dist/apple/YourCrate.xcframework + Package.swift
# Produces: ./dist/android/jniLibs/<abi>/libyour_crate.so + Kotlin bindings
# Produces: ./dist/wasm/pkg/*.wasm + TypeScript bindings + npm package
Android ABI selection is configurable in boltffi.toml:
[]
= ["arm64"]
Apple slice selection is configurable too:
[]
= ["arm64"]
= ["arm64"]
= true
= ["arm64"]
Any Apple architecture list can be set to [] to exclude that slice family. For example, set
ios_architectures = [] when you want simulator-only Apple packaging from a config overlay. BoltFFI
still requires at least one Apple slice overall.
When architectures is omitted, BoltFFI keeps the existing default Android matrix:
arm64, armv7, x86_64, and x86. boltffi pack android --no-build now validates that each
configured ABI already has a built Rust static library and ignores stale artifacts for
unconfigured ABIs.
You can also apply per-invocation overlays without mutating the tracked base config.
BoltFFI still requires a base boltffi.toml, then merges the overlay on top for that one command:
This is useful for CI, release builds, or machine-local overrides. boltffi init does not accept
--overlay.
Use it from Swift, Kotlin, or TypeScript:
let d = distance(a: Point(x: 0, y: 0), b: Point(x: 3, y: 4)) // 5.0
val d = distance(a = Point(x = 0.0, y = 0.0), b = Point(x = 3.0, y = 4.0)) // 5.0
import { distance } from 'your-crate';
const d = distance({ x: 0, y: 0 }, { x: 3, y: 4 }); // 5.0
The generated bindings use each language's idioms. Swift gets async/await. Kotlin gets coroutines. TypeScript gets Promises. Errors become native exceptions.
Supported languages
| Language | Status |
|---|---|
| Swift | Full support |
| Kotlin | Full support |
| Java | Full support |
| WASM/TypeScript | Full support |
| C | Partial |
| Python | In progress |
| C++ | Planned |
| C# | In progress |
| Ruby | Planned |
| Dart | Planned |
| Scala | Planned |
| Go | Planned |
| Lua | Potential |
| R | Potential |
Want another language? Open an issue.
Installation
Add to your Cargo.toml:
[]
= "0.1"
[]
= ["cdylib", "staticlib"]
Documentation
Alternative tools
Other tools that solve similar problems:
- UniFFI - Mozilla's binding generator, uses serialization-based approach
- Diplomat - Focused on C/C++ interop
- cxx - Safe C++/Rust interop
Contributing
Contributions are warmly welcomed 🙌
License
BOLTFFI is released under the MIT license. See LICENSE for more information.