Clone this repo:
  1. 65833f1 Separate cpp_layout_equivalent and bridged type paths in cc_bindings_from_rs by Taylor Cramer · 43 minutes ago main upstream/main
  2. 8f93ce9 Restore mdBook part titles in Crubit documentation summary. by Taylor Cramer · 56 minutes ago
  3. 10ddead Add more end-to-end coverage for enum_api_v2. by Googler · 3 hours ago
  4. 26d6a78 Adapt to upstream rustc change in `AttributeKind::Deprecated` by Michael VanBemmel · 3 hours ago
  5. 94f09a8 Support packed structs in `rs_bindings_from_cc`. by Googler · 6 hours ago

Crubit: C++/Rust Bidirectional Interop Tool

Docs rust workflow GitHub

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:

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, and Default become 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 as PartialEq, Add, Clone, and Drop.
  • 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 systemC++ calling Rust (cpp_api_from_rust)Rust calling C++ (rust_api_from_cpp)
BazelSupported (example)Supported
CMakeSupported (example)Not yet
OtherThrough the cargo cpp_api_from_rust subcommandNot 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:

See the full documentation at crubit.rs for additional reference material.

Test Matrix

NightlyStable
nightly-0stable-0
nightly-1stable-1
nightly-2
nightly-3
nightly-4
nightly-5
nightly-6