#proc-macro #codegen

macro boltffi_macros

Procedural macros for BoltFFI - #[data], #[export], #[class] attributes

10 releases (6 breaking)

Uses new Rust 2024

new 0.25.0 May 11, 2026
0.24.1 Apr 24, 2026
0.23.1 Apr 8, 2026
0.22.1 Apr 2, 2026
0.1.1 Feb 17, 2026

#2224 in Procedural macros

Download history 3/week @ 2026-02-15 33/week @ 2026-02-22 36/week @ 2026-03-01 18/week @ 2026-03-08 836/week @ 2026-03-15 673/week @ 2026-03-22 844/week @ 2026-03-29 1267/week @ 2026-04-05 840/week @ 2026-04-12 1212/week @ 2026-04-19 1164/week @ 2026-04-26 1078/week @ 2026-05-03

4,343 downloads per month
Used in 2 crates

MIT license

550KB
14K SLoC

BoltFFI

A high-performance multi-language bindings generator for Rust. Up to 1,000x faster than UniFFI. Up to 450x faster than wasm-bindgen.

Join our Discord

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 boltffi::*;

#[data]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

#[export]
pub fn distance(a: Point, b: Point) -> f64 {
    let dx = b.x - a.x;
    let dy = b.y - a.y;
    (dx * dx + dy * dy).sqrt()
}

Run BoltFFI for the targets you need:

boltffi pack apple
# Produces: ./dist/apple/YourCrate.xcframework + Package.swift

boltffi pack android
# Produces: ./dist/android/jniLibs/<abi>/libyour_crate.so + Kotlin bindings

boltffi pack java
# Produces: ./dist/java/native/<host-target>/libyour_crate_jni.* + Java bindings

boltffi pack wasm
# Produces: ./dist/wasm/pkg/*.wasm + TypeScript bindings + npm package

boltffi pack csharp
# Produces: ./dist/csharp/packages/*.nupkg with RID native assets

Android ABI selection is configurable in boltffi.toml:

[targets.android]
architectures = ["arm64"]

Apple slice selection is configurable too:

[targets.apple]
ios_architectures = ["arm64"]
simulator_architectures = ["arm64"]
include_macos = true
macos_architectures = ["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:

boltffi --overlay boltffi.ci.toml pack android
boltffi --overlay boltffi.release.toml pack all --release

This is useful for CI, release builds, or machine-local overrides. boltffi init does not accept --overlay.

Use it from Swift, Kotlin, Java, C#, 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
double d = MyLib.distance(new Point(0.0, 0.0), new Point(3.0, 4.0)); // 5.0
double d = MyLib.Distance(new Point(0.0, 0.0), new Point(3.0, 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. Java gets CompletableFuture and functional interfaces. C# gets Tasks and async enumerables. TypeScript gets Promises. Errors become native exceptions.

Supported languages

Language Status
Swift Full support
Kotlin Full support
Java Full support
C# Full support
WASM/TypeScript Full support
C Partial
Python In progress
C++ Planned
Ruby Planned
Dart In progress
Scala Planned
Go Planned
Lua Potential
R Potential

Want another language? Open an issue.

Installation

cargo install boltffi_cli

Add to your Cargo.toml:

[dependencies]
boltffi = "0.1"

[lib]
crate-type = ["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.

Dependencies

~100–455KB
~11K SLoC