Crubit: C++/Rust Bidirectional Interop Tool
Crubit is a bidirectional bindings generator for C++ and Rust, allowing users to:
- Automatically reuse existing crates and headers: no IDL or glue code is necessary.
- Call back and forth between Rust and C++ with no performance cost: no copies or heap allocations required.
- Use idiomatic types from both Rust and C++, such as
Vec,Result,Option,std::vector,std::unique_ptr,std::shared_ptr, and more!
Example
Calling Rust from C++
Consider the following Rust library:
#![allow(unused)]
fn main() {
pub struct Account {
pub id: u64,
pub balance: f64,
}
impl std::fmt::Display for Account { ... }
// Takes shared references, mapped to const references in C++.
pub fn calculate_interest(account: &Account, rate: f64) -> f64 { ... }
// Takes a string slice, mapped to rs_std::StrRef in C++.
pub fn is_valid_username(username: &str) -> bool { ... }
}
You can call these Rust functions from C++:
#include "path/to/account.h"
#include <iostream>
void demo() {
account::Account my_account{.id = 123, .balance = 1000.0};
double interest = account::calculate_interest(my_account, 0.05);
// my_account is printable because Account implements Display in Rust!
std::cout << my_account << std::endl;
if (account::is_valid_username("bob")) {
std::cout << "Valid user" << std::endl;
}
}
Calling C++ from Rust
Consider the following C++ header:
#include <string_view>
#include <optional>
#include <memory>
struct User {
int id;
double balance;
};
std::optional<User> FindUser(int id);
std::unique_ptr<User> CreateUser(std::string_view name, int id);
You can call these C++ functions from Rust:
use cpp_std::unique_ptr;
use ffi_11::c_int;
use user_api::{CreateUser, FindUser, User};
let id: c_int = 123;
let user: Option<User> = FindUser(id);
if let Some(u) = user {
let balance: f64 = u.balance;
println!("User {} has balance {}", u.id, balance);
}
let new_user: unique_ptr<User> = CreateUser("Alice".into(), 456);
Why Crubit?
- No hand-written glue code. Crubit reads C++ headers and Rust crates directly and generates bindings in both directions. Your signatures can’t drift out of sync with the code they wrap, and you don’t need to maintain a separate interface file.
- More than the C ABI. Crubit can call functions that aren’t
extern "C", pass structs and enums by value, and match the memory layout of Rust structs and enums that aren’t#[repr(C)]. - Idiomatic types on both sides. References, slices,
&str,Option,Result, tuples, smart pointers,std::vector/Vec, callbacks,absl::Status, and more map to their natural counterparts. See the status page for the full table. - Traits and operators, too. C++ can call Rust trait implementations,
and Rust traits such as
Display,Clone, andDefaultbecome the matching C++ operators and special member functions. In the other direction, C++ operator overloads and special member functions become the matching Rust traits, such asPartialEq,Add,Clone, andDrop. - Safer than hand-written FFI. Crubit generates bindings from the real
C++ headers and Rust crates at build time, with the same compiler and
flags, so mismatched ABIs, struct layouts, and signatures can’t creep in.
C++ references get Rust lifetimes through lifetime elision and
lifetime annotations, so
the Rust borrow checker can check them. Slices, strings,
Option, and other types carry their bounds and invariants across the boundary instead of degrading to raw pointers. See Memory Safety for the details.
Getting Started
1. Set up your build
Crubit runs as part of your build, so that the bindings always match the code they wrap. Pick your build system:
| Build system | C++ calling Rust (cpp_api_from_rust) | Rust calling C++ (rust_api_from_cpp) |
|---|---|---|
| Bazel | Supported (example) | Supported |
| CMake | Supported (example) | Not yet |
| Other | Through the cargo cpp_api_from_rust subcommand | Not yet |
Crubit works with nightly Rust toolchains and with the two most recent stable
releases, as long as the rustc-dev component is installed (see the
test matrix below). There are no binary releases yet; pin a
commit of this repository.
Build System Integrations covers
toolchain requirements and how to set up each build system.
2. Use your Rust and C++ APIs together
Add a cpp_api_from_rust or rust_api_from_cpp target next to your library,
and start calling it from the other language. Each direction has a walkthrough
that explains what the generated API looks like and how to customize it, plus
copy-pastable examples that include snapshots of the generated bindings:
- C++ calling Rust:
C++ Bindings for Rust Libraries walkthrough and
examples/rust/. - Rust calling C++:
Rust Bindings for C++ Libraries walkthrough and
examples/cpp/.
The links on the sidebar contain additional reference material.