Note: This post was originally published on My Blog.
As developers, we often face a difficult choice when building high-performance libraries:
- Write in a high-level language (JS/Python) for ease of use but sacrifice raw performance.
- Write in a low-level language (Rust/C++) and deal with the nightmare of maintaining bindings for multiple languages.
If you've ever chosen the latter, you know the struggle. You end up juggling pyo3 for Python and napi-rs for Node.js. You have snippets of "glue code" scattered everywhere, inconsistent APIs across languages, and a CI pipeline that breaks if you look at it wrong.
I built BridgeRust to change that.
What is BridgeRust?
BridgeRust is a unified framework that lets you write your library once in Rust and automatically deploy native, high-performance bindings to both Python and Node.js (with more languages coming).
It's not just a binding generator; it's a complete toolkit that handles the boring stuff:
- Project Scaffold: Set up a multi-language project in seconds.
-
Unified Macro System: One
#[export]macro for all languages. -
Type Conversions: Automatic mapping of
Vec,Option,Result, and more. - Building & Publishing: Integrated CLI to build wheels and npm packages.
The "Write Once" Magic πͺ
Here is how it actually looks to expose Rust code to the world using BridgeRust.
1. Initialize
cargo install bridge
bridge init my-awesome-lib
cd my-awesome-lib
2. Write Rust
In src/lib.rs, you simply tag your functions and structs with #[export]. You don't need to know the internals of the Python C-API or V8.
use bridgerust::export;
#[export]
pub fn greet(name: String) -> String {
format!("Hello, {}! This is logic from Rust.", name)
}
#[export]
pub struct Point {
pub x: f64,
pub y: f64,
}
#[export]
pub fn fast_math(points: Vec) -> f64 {
// Heavy computation here...
points.iter().map(|p| p.x + p.y).sum()
}
3. Build
bridge build --all
That's it. BridgeRust generates:
- A Python wheel (
pip install ...) - A Node.js native addon (
npm install ...)
Consuming the Library
The best part is that the generated bindings feel native in every language. We handle idiomatic naming conventions automatically (snake_case for Python, camelCase for JS).
Python:
import my_awesome_lib
print(my_awesome_lib.greet("Pythoneer"))
p1 = my_awesome_lib.Point(1.0, 2.0)
result = my_awesome_lib.fast_math([p1])
Node.js:
const lib = require("my-awesome-lib");
console.log(lib.greet("JavaScripter"));
const p1 = new lib.Point(1.0, 2.0);
// Notice fast_math became fastMath automatically!
const result = lib.fastMath([p1]);
Real-World Example: Embex
This isn't just a toy project. We built Embex, a universal vector database client, using BridgeRust. It connects to Pinecone, Qdrant, LanceDB, and more.
By using BridgeRust, we maintained a single codebase for the core logic (connection pooling, retry strategies, SIMD operations) while offering native packages for both Python (pip install embex) and Node.js (npm install @bridgerust/embex).
Why I Built This
-
Zero Boilerplate: I was tired of writing manual
PyModuleornapi_register_moduledefinitions. - Performance: I wanted zero-cost abstractions where possible.
- Consistency: I needed to ensure Python and JS users got exactly the same features and bug fixes at the same time.
Try it out!
BridgeRust is open source and I'd love to hear your feedback on the API design.
- Check out the repo: github.com/bridgerust/bridgerust
- Read the docs: bridgerust.dev
Stop writing glue code. Start building bridges. π¦π
Have you tried maintaining cross-language bindings before? Let me know in the comments what your biggest pain point was!
Top comments (0)