Add the movable_return_values feature: return non-C++-movable types as rs::Movable<T>.

Rust functions which return a non-C++-movable type by value currently return
a bare `T` from the generated C++ API, which can only be received through
guaranteed copy elision (`T x = f();`). In particular, the result cannot be
passed on to another Rust function taking `T` by value, since such parameters
are spelled `rs::Movable<T>` in C++; callers had to relocate the value into an
`rs::Movable<T>` by hand through a `crubit::Slot<T>`.

With the new `movable_return_values` feature (aspect hint
`//third_party/crubit/features:movable_return_values`), such functions return
`rs::Movable<T>` instead, mirroring how by-value parameters are passed. This
makes `consume_unmovable(make_unmovable(42))` work directly.

To keep existing callers compiling, `rs::Movable<T>` gains an implicit
conversion from an rvalue `Movable<T>` to `T`. The conversion yields a prvalue
(via `crubit::UnsafeTakeValue`), so guaranteed copy elision initializes the
target directly and no C++ move constructor of `T` is required. As a result,
`T x = f();`, `const T& x = f();`, `return f();` from a function returning `T`,
and passing `f()` to a parameter of type `const T&` or `T` all keep working.
Only rvalues convert, so a named `rs::Movable<T>` must be unwrapped with
`std::move`; the conversion aborts if the `Movable` is already valueless.

The thunk ABI is unchanged: the thunk still writes a bare `T` into the return
slot, and only the public C++ API wraps it. Non-movable types nested inside a
returned `std::tuple` were already wrapped in `rs::Movable<T>` regardless of
this feature.

The feature is not yet part of `all` or `SUPPORTED_FEATURES`. To exercise it
and to show its effect on callers, it is enabled on every cc_bindings_from_rs
end-to-end test crate whose API returns a non-C++-movable type. The only caller
changes this required were `auto x = T::new_(...)` sites that then accessed
members of `x`, which now spell the type (`T x = T::new_(...)`).

PiperOrigin-RevId: 1004416087
46 files changed
tree: d4c3c9ba38b3fa0c47a0b24c5433423c3d6a3e6e
  1. .github/
  2. assets/
  3. bazel/
  4. buildenv/
  5. cargo/
  6. cc_bindings_from_rs/
  7. common/
  8. crubit_explorer/
  9. docs/
  10. doxygen/
  11. examples/
  12. features/
  13. lifetime_analysis/
  14. lifetime_annotations/
  15. migrator/
  16. nullability/
  17. patches/
  18. rs_bindings_from_cc/
  19. support/
  20. theme/
  21. .bazelignore
  22. .bazelrc
  23. .bazelversion
  24. .gitignore
  25. book.toml
  26. BUILD
  27. BUILD.gn
  28. Cargo.Bazel.lock
  29. Cargo.lock
  30. Cargo.toml
  31. CMakeLists.txt
  32. CODE_OF_CONDUCT
  33. CONTRIBUTING.md
  34. crubit.gni
  35. LICENSE
  36. MODULE.bazel
  37. MODULE.bazel.lock
  38. README.md
  39. rust-toolchain.toml
  40. rustfmt.toml
README.md

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