Rust bindings for C++ libraries
When a C++ library enables Crubit, that library can be used directly from Rust. This page documents roughly what that entails, and additional subpages (available in the left-hand navigation) document specific aspects of the generated bindings.
Tip: The code examples below are pulled straight from https://github.com/google/crubit/tree/main/examples/cpp/function/. The other examples in https://github.com/google/crubit/tree/main/examples/cpp/ are also useful. If you prefer just copy-pasting something, start there.
How to use Crubit
Crubit allows you to call some C++ interfaces from Rust. It supports
functions, classes and structs, and
enums, as well as concrete class template specializations (such as
MyTemplate<int>) that appear in your API. Crubit does not support advanced
features like function templates, uninstantiated templates, or virtual
inheritance.
The rest of this document goes over how to create a C++ library that can be called from Rust, and how to actually call it from Rust. The quick summary is:
-
Define a
rust_api_from_cpptarget in the sameBUILDfile as yourcc_library. -
Add the generated
.hinttarget (e.g.:<name_of_rust_target>.hint) to theaspect_hintsof yourcc_library. -
Depend on the
rust_api_from_cpptarget in thedepsof your Rust target. In Rust, the crate is named after thecc_library, not therust_api_from_cpptarget. See Crate name.
The bindings can be previewed by building the cc_library with
--config=crubit-genfiles. See Look at the generated bindings.
$ bazel build --config=crubit-genfiles //path/to:my_cc_library
Write a cc_library target
The first part of creating a library that can be used by Crubit is to write a
cc_library target. For example:
#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_CPP_FUNCTION_EXAMPLE_H_
#define THIRD_PARTY_CRUBIT_EXAMPLES_CPP_FUNCTION_EXAMPLE_H_
#include <stdint.h>
namespace gshoe {
inline int32_t add_two_integers(int32_t x, int32_t y) { return x + y; }
} // namespace gshoe
#endif // THIRD_PARTY_CRUBIT_EXAMPLES_CPP_FUNCTION_EXAMPLE_H_
If you write a BUILD target as normal, it will not actually get Crubit bindings, but we’ll start from there:
# This BUILD rule is here for exposition only. This is an example of what happens if
# you _don't_ set things up for Crubit.
cc_library(
name = "example_lib_broken",
hdrs = ["example.h"],
)
Enable Crubit on a target
To enable Crubit on a C++ target, you must define a rust_api_from_cpp target
associated with it, and link them using aspect_hints.
Behind the scenes, Rust APIs are generated via Bazel aspects1
which run on the cc_library target. When examining a cc_library, Rust API
generation looks for the aspect_hints so that it can find the corresponding
rust_api_from_cpp target.
Define a rust_api_from_cpp target in the same BUILD file as your
cc_library (loaded via
load("//bazel:rust_api_from_cpp.bzl", "rust_api_from_cpp")), and add its .hint target to the aspect_hints of the
cc_library:
cc_library(
name = "example_lib",
hdrs = ["example.h"],
# Opt into using Crubit.
aspect_hints = [":example_lib_rust.hint"],
)
rust_api_from_cpp(
name = "example_lib_rust",
cpp_target = ":example_lib",
)
The .hint target is automatically created by the rust_api_from_cpp macro
(named <name>.hint) and is used to avoid circular dependencies between the C++
library and the generated Rust API.
Note that having Rust callers does constrain library evolution. Certain changes cannot be made in C++ without breaking Rust callers, unless care is taken. crubit.rs/cpp/cookbook#compatibility
Look at the generated bindings
To examine the generated Rust bindings for a C++ library, build the cc_library
target with --config=crubit-genfiles:
$ bazel build --config=crubit-genfiles //examples/cpp/function:example_lib
Build the cc_library itself, not the rust_api_from_cpp target. The bindings
are generated by an aspect that runs on the cc_library, so building the
rust_api_from_cpp target with this flag doesn’t print any generated files.
This is the best way to preview the generated bindings for a given C++ target right now. You might end up using this a lot, so keep it in your shell history.
If you run the above command, you should see some output like the following:
Aspect //rs_bindings_from_cc/bazel_support:rust_bindings_from_cc_aspect.bzl%rust_bindings_from_cc_aspect of //examples/cpp/function:example_lib up-to-date:
bazel-bin/examples/cpp/function/example_lib_rust_api_impl.cc
bazel-bin/examples/cpp/function/example_lib_rust_api.rs
bazel-bin/examples/cpp/function/example_lib_namespaces.json
These files are the generated bindings which are used under the hood when depending on a C++ target from Rust. They consist of:
- The supporting C++ code to glue Rust and C++ together. (The
.ccfile.) - The public Rust interface. (The
.rsfile.) - Supporting internal implementation details. (The
.jsonfile.)
You don’t need to check them in, as they are regenerated automatically whenever you build a Rust build target which depends on C++.
The .rs file is the interesting one for end users. It should contain an
actually useful API for the target:
// Automatically @generated Rust bindings for the following C++ target:
// //examples/cpp/function:example_lib
#![rustfmt::skip]
#![feature(custom_inner_attributes)]
#![allow(stable_features)]
#![allow(improper_ctypes)]
#![allow(nonstandard_style)]
#![allow(unused)]
#![allow(deprecated)]
#![allow(unknown_lints, suspicious_runtime_symbol_definitions)]
#![deny(warnings)]
pub mod gshoe {
#[inline(always)]
pub fn add_two_integers(x: i32, y: i32) -> i32 {
unsafe { crate::detail::__rust_thunk___ZN5gshoe16add_two_integersEii(x, y) }
}
}
// namespace gshoe
mod detail {
#[allow(unused_imports)]
use super::*;
unsafe extern "C" {
pub(crate) unsafe fn __rust_thunk___ZN5gshoe16add_two_integersEii(x: i32, y: i32) -> i32;
}
}
When something is missing
If Crubit can’t generate bindings for a function or type, the build doesn’t fail
by default. Crubit leaves the item out of the generated .rs file and writes a
comment in its place explaining why:
#![allow(unused)]
fn main() {
// error: function `UseNotCrubitExposed` could not be bound
// Unsupported parameter type `NotCrubitExposed not_crubit_exposed`:
// Crubit is not enabled on defining target:
// //path/to:not_crubit_exposed
}
The first error you see is usually in the Rust code that calls the missing item,
which doesn’t say why it’s missing. Search the generated .rs file for the
item’s name to find the reason. The reason often names a type rather than the
function itself; fix or wrap that type, and the function gets bindings too.
crubit.rs/errors explains the most common reasons.
To make a missing item fail the build instead, apply the CRUBIT_MUST_BIND
attribute to it. See Customizing the generated bindings.
Use a C++ library from Rust
To depend on a C++ library from Rust, add the corresponding rust_api_from_cpp
target to your Rust target’s deps:
rust_binary(
name = "main",
srcs = ["main.rs"],
edition = "2024",
# Declare a dependency on Rust bindings for calling into the C++ `example_lib` library:
deps = [":example_lib_rust"],
)
At that point, the bindings are directly usable from Rust. The interface is
identical to the .rs file previewed earlier, but can be used directly:
fn main() {
let sum = example_lib::gshoe::add_two_integers(2, 2);
println!("sum = {sum}");
}
Crate name
The Rust crate is named after the cc_library, not the rust_api_from_cpp
target. In the example above, the crate is example_lib, not
example_lib_rust. C++ namespaces become Rust modules inside it, so
gshoe::add_two_integers in C++ is example_lib::gshoe::add_two_integers in
Rust.
The crate name is the cc_library’s target name with every character that isn’t
a letter or digit replaced by _. The package path isn’t part of it. Two
exceptions:
- If the name starts with a digit, it gets an
nprefix:3d_mathbecomesn3d_math. - A target named
corebecomescore_plus the last directory of its package, so that it doesn’t shadow Rust’scorecrate://foo/bar:corebecomescore_bar.
Common Errors
See crubit.rs/errors
Unsupported features
Some features are either unsupported, or else only supported with experimental feature flags . In order to get bindings for a C++ interface, that interface must only use the subset of features currently supported.
The way to work around this kind of problem, in all cases, is to wrap or hide the problematic interface behind an interface Crubit can handle:
- Hide unsupported types behind a wrapper. For example, a
std::set<T>is not supported, but a struct which wraps astd::set<T>is. crubit.rs/errors/unsupported_type describes the process in more detail. - Wrap unsupported functions, in general, behind wrappers.
- Embed C++ directly in Rust: Use
inline_cpp!to allow embedding arbitrary C++ code within a Rust target using therust_library_with_embedded_cppBazel rule.
-
Crubit is an aspect: an automatically generated entity that exists on every build target. It is disabled by default, so that Rust callers don’t accidentally impose on C++ libraries that weren’t expecting them.
Aspects allow Crubit to fully understand the dependency graph: the bindings for X are in the Crubit aspect of X. This allows Crubit to generate bindings which themselves rely on bindings: if a function in target
Areturns a struct from targetB, we know that the bindings forAwill depend on the bindings forB. Because Crubit is an aspect, it already knows the name of the bindings forB: it’s simply the Crubit aspect onB!Without aspects, or something like aspects, you would need to write down, for every library, the location of its Rust bindings. There is no need for that kind of boilerplate when aspects are involved, and that is why most things shaped like Crubit use aspects. For example, protocol buffers use aspects for their generated implementations in multiple languages. (They also use named rules, but the rules simply re-export the aspect, and the underlying aspect is what is used within the rule for referring to transitive dependencies.) Thanks to aspects, the
proto_librarydoesn’t need to re-specify “ah, and the Go proto is named'x'”.Be not afraid! Aspects are what make transitive dependencies work seamlessly, without boilerplate. So when you see aspect this, or aspect that, remember: this is a Good Thing. ↩