Crubit: generating C++ and Rust bindings in both directions
A bidirectional bindings generator for C++ and Rust.
At a glance
- What is it?
- Crubit is Google's bidirectional bindings generator for C++ and Rust, which turns a Rust library into a C++ header or a C++ header into Rust modules, mapping references, string slices, optionals and smart pointers across the boundary. It is built on Bazel and Cargo, distributed under Apache-2.0 with LLVM exceptions, and publishes no tagged releases, so the status page rather than a version number is the contract.
- Who is it for?
- Adopt Crubit if you maintain a C++ library that should be callable from Rust, or a Rust library with a C++ consumer, and you are willing to live with a tool whose feature set is described on a status page rather than pinned by a release, since the generated API follows the C++ conventions of the source rather than Rust's.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A generator, not a runtime bridge
Crubit is a bidirectional bindings generator for C++ and Rust, with the goal of integrating the C++ and Rust ecosystems.
The word generator is the important part. Crubit is not a runtime that sits between two languages, and it is not a set of hand written wrapper types you link into your application. It is a compiler-like tool that reads a Rust library or a C++ header and produces the bindings for the other side. The output is ordinary C++ or ordinary Rust that you compile and link yourself, with no Crubit code in the final binary.
That design has a direct consequence for the reader. The problem it addresses is not calling across a language boundary at runtime, which is what a bridge library solves, but exposing a library written in one language to callers who write the other. If you have a Rust parsing library and a C++ application that needs it, or the reverse, the choice has historically been to hand-write a wrapper crate or a header, and to keep it in step with the original API by hand.
The intended user is therefore a library author on one side of the boundary with consumers on the other, and the payoff is in maintenance rather than in performance. A binding that is generated cannot drift from the API it was generated from.
The project is hosted by Google, the primary language is C++, the homepage is crubit.rs, and the last push to `main` was on 2026-10-19. The repository is not archived, and there are no GitHub releases, which is the single most important operational fact about it: there is no version to pin.
Instead, the README points to a status page at crubit.rs/overview/status for an overview of the currently supported features. That page, rather than a semantic version, is the contract Crubit offers, and reading it is the first thing to do before evaluating the project.
The two directions, shown as generated API
The README's two examples are the clearest available statement of what the tool does, so they are worth reading closely.
The first direction is calling Rust from C++. The Rust library defines a struct with public fields, implements `Display` for it, and exposes two free functions. One takes a shared reference to the account and a rate, the other takes a string slice:
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 { ... }The generated C++ side is what a C++ developer writes:
#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;
}
}Two features of that output are worth calling out. The struct is constructed with designated initialisers, which is C++20 syntax, so the generated header has a language level requirement. And the `Display` implementation in Rust is what makes the C++ `std::cout << my_account` work at all, which is a nice illustration of the generator carrying language idioms across rather than flattening everything to a C ABI.
The other direction, calling C++ from Rust, starts from a header that uses `std::optional` and `std::unique_ptr`:
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);The Rust side is idiomatic Rust, with `Option` for the `std::optional` return and `unique_ptr` for the `std::unique_ptr` return, and with a string literal converted with `.into()`. The C++ types are not hidden behind an opaque handle; the mapping is visible in the generated module names, `cpp_std` and `ffi_11`.
The type mapping, and where the seams are
Every interop tool lives or dies by its type mapping, and Crubit's mapping is visible in the examples rather than buried in a manual.
Shared references in Rust map to const references in C++. A string slice maps to `rs_std::StrRef` in C++, which is a view type rather than a copied string, so the mapping preserves the borrowing semantics of the source rather than pretending the boundary is free. A `std::optional` return maps to `Option` in Rust, and a `std::unique_ptr` return maps to `unique_ptr` from `cpp_std`. The `int` parameter maps to `c_int` from `ffi_11`, which is C's `int` type.
The repository layout shows the depth of the machinery behind those mappings. There is a `nullability/` directory, which is where the question of how C++ nullability annotations map to Rust's `Option` is handled. There is a `lifetime_analysis/` directory and a `lifetime_annotations/` directory, which is where the tool reasons about borrow lifetimes when it lowers Rust references into C++ references. There is a `migrator/` directory for rewriting code as the mapping changes, a `features/` directory for feature flags, and a `crubit_explorer/` for browsing the generated API. The `doxygen/` directory suggests the generated C++ side is documented with Doxygen.
The direction of the mapping is a deliberate asymmetry rather than an oversight. Rust's borrow checker has no direct C++ equivalent, and C++ has RAII and value semantics that Rust does not share, so a bridge is a place where semantics get lost. Crubit's approach of lowering Rust references to C++ const references and C++ smart pointers to Rust smart pointer wrappers means the boundary is a thin, explicit layer rather than a hand written shim per function.
The cost is that the generated code inherits the assumptions of the source language. A C++ caller of a Rust function gets a C++ API with C++ lifetimes, and a Rust caller of a C++ function gets Rust semantics, and the interesting questions are all about the seams, which is why the status page and the walkthroughs matter more here than for a typical code generator.
Bazel, Cargo and CMake all in the same tree
Crubit is a toolchain project, and the repository reflects that with three build systems living side by side.
At the root there is a Bazel setup, with `BUILD` files, `BUILD.gn`, `MODULE.bazel` and its lock file, `.bazelrc`, `.bazelignore` and a pinned `.bazelversion`. There is a `bazel/` directory for the Bazel rules the project ships. There is a `CMakeLists.txt` at the root and an `examples/build_systems/` directory, which is where the CMake path is exercised. And there is a Cargo workspace whose `Cargo.toml` lists the Rust members of the project, with the `cargo/` directory holding the cargo subcommands, `cc_bindings_from_rs/cargo-cpp_api_from_rust` and `rs_bindings_from_cc`, each of which is a cargo command that runs the generator for one direction.
The Cargo manifest is also where the licence and toolchain facts are recorded. The file header states it is part of the Crubit project under the Apache License v2.0 with LLVM Exceptions, with the SPDX identifier `Apache-2.0 WITH LLVM-exception`. The workspace uses resolver 2 and edition 2024, and there is a `rust-toolchain.toml` at the root pinning the toolchain. The `support/` directory holds the runtime support crates, `ffi_11` for C integer types, `crubit_bridge_rust` for the bridge itself, `crubit_hash_rust`, `crubit_annotate` and `crubit_support`.
Two of the workspace dependencies reveal the compiler-level nature of the tool. `ra_ap_rustc_lexer` is a Rust compiler lexer, and `rustc_stable_hash` is the hashing scheme the Rust compiler uses to identify crates and items. A bindings generator that produces stable identifiers for Rust items needs to agree with the compiler about what those identifiers are, which is why it depends on the compiler's own crates rather than reimplementing the hashing.
The practical upshot is that Crubit is not a plugin you drop into an arbitrary build. It expects Bazel, or Cargo, or CMake, and the walkthroughs in `examples/rust/` and `examples/cpp/` show the code for each direction with the build steps alongside.
The test matrix and the status page as the real contract
Because there are no releases, the two artefacts that tell you what Crubit does are the status page and the test matrix.
The status page at crubit.rs/overview/status is described as an overview of the current supported features. For a generator whose job is to be correct about a large set of language constructs, that page is the specification. It is also the thing to read before you commit, because the answer to whether Crubit can do X is in the feature list rather than in a version number.
The test matrix is the second artefact, and it is a good sign. The README shows a table of badges for nightly-0 through nightly-6 and stable-0 and stable-1, each pointing at a status endpoint under the repository's `test-matrix` branch. Seven nightly configurations against two stable ones is a lot of continuous integration for a bindings generator, and the split suggests that the nightly lanes are where the bleeding edge of the feature set is exercised while the stable lanes track what is considered releasable.
The `examples/` directory then provides the check that matters to a user, because the walkthroughs at crubit.rs/rust and crubit.rs/cpp come with copy-pastable example code and spanshots of what the generated bindings look like. A screenshot of the generated header is a much better acceptance test than a feature list, because it shows you the exact API your callers will write against.
The repository also carries `docs/`, a `book.toml` for the documentation book, `doxygen/` for the C++ side, `patches/`, `buildenv/`, `assets/` and a `theme/` directory, plus a `CONTRIBUTING.md` and a `CODE_OF_CONDUCT`.
What the repository does not tell you is how a project should consume Crubit in production. There is no versioned artefact to depend on, so the realistic integration is to vendor the generator, pin a commit, and accept that you own the upgrade. That is a meaningful operational commitment for a tool whose feature set is still described on a status page.
Licence, maturity, and the cases where Crubit is the wrong tool
The licence needs one note. The project metadata records the licence as other, a custom licence that GitHub cannot classify, while the workspace `Cargo.toml` states that the project is under the Apache License v2.0 with LLVM Exceptions, with the SPDX identifier `Apache-2.0 WITH LLVM-exception`, and points at the `LICENSE` file. The `LICENSE` file at the repository root is the one to read. This is a description of the files rather than legal advice.
On maturity, the honest summary is that Crubit is a Google project with a large CI matrix, an extensive implementation directory and a public status page, and it has no releases and therefore no version you can depend on. Those two facts are not contradictory, but they do mean you are adopting a moving target with a project that has the resources to keep moving.
There are three clear cases where it is the wrong tool.
The first is when you need a stable dependency. If your build must resolve a fixed version of the generator and be reproducible months later, a repository with no releases and a `main` branch as the only coordinate is a poor fit, whatever the quality of the code.
The second is when the type surface is outside the mapping. Crubit maps shared references, string slices, optionals and smart pointers. The harder cases in practice are the ones the language models disagree about, namely object lifetimes across the boundary, callbacks and closures in both directions, and C++ templates. The `lifetime_analysis/` and `nullability/` directories are evidence that these are the hard parts, and the status page is where you find out which of them are covered.
The third is when the build system is the constraint. Crubit assumes Bazel, Cargo or CMake, and the cargo subcommands are the intended entry point.
The alternative approach, and the one to weigh against, is a C ABI layer written by hand. A hand-written `extern "C"` shim with an opaque handle and a handful of accessor functions works with any build system, any compiler pair, and any version of the languages, and it never surprises you. The difference in approach is that Crubit generates a wide, idiomatic surface that exposes the whole API with language-appropriate types, while the hand-written shim gives you a narrow, stable, and slightly less pleasant interface that you must maintain yourself.
Editorial conclusion
Adopt Crubit if you maintain a C++ library that should be callable from Rust, or a Rust library with a C++ consumer, and you are willing to live with a tool whose feature set is described on a status page rather than pinned by a release, since the generated API follows the C++ conventions of the source rather than Rust's. Do not adopt it if you need a stable versioned artifact, if your build system is neither Bazel nor Cargo nor CMake, or if your types fall outside the documented mapping set. Verify three things first: that the status page lists the C++ and Rust features your interface actually needs, that the examples for your direction build in your own build system, and that the licence file matches the Apache-2.0 with LLVM exception stated in the workspace manifest.
Frequently asked questions
What does the Crubit project do?
It is a bidirectional bindings generator for C++ and Rust, aiming to integrate the two ecosystems. It generates C++ headers from Rust libraries and Rust modules from C++ headers, so the bindings are compiled into your project rather than requiring a Crubit runtime.
How does Crubit map Rust types into C++?
The README's example shows shared references mapped to const references and a string slice mapped to `rs_std::StrRef` in C++, and a Rust `Display` implementation making the generated struct printable with `std::cout`. In the other direction, `std::optional` becomes `Option` and `std::unique_ptr` becomes `unique_ptr` from `cpp_std`.
Which build systems does Crubit support?
The repository carries a Bazel setup with `MODULE.bazel` and a pinned `.bazelversion`, a root `CMakeLists.txt` with examples under `examples/build_systems/`, and a Cargo workspace whose `cargo/` directory holds the cargo subcommands for each direction.
Is there a released version of Crubit?
The repository has no GitHub releases. Instead, the README points to a status page at crubit.rs/overview/status for an overview of the currently supported features, and the test matrix shows nightly-0 to nightly-6 and stable-0 and stable-1 CI configurations.
What licence is Crubit released under?
The workspace Cargo.toml states the project is under the Apache License v2.0 with LLVM Exceptions, with the SPDX identifier Apache-2.0 WITH LLVM-exception, and points at the LICENSE file at the repository root, which is the document to read.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/google-crubit)Was this useful?