C++ bindings for Rust libraries
Rust libraries can be used directly from C++. 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/rust/function/. The other examples in https://github.com/google/crubit/tree/main/examples/rust/ are also useful. If you prefer just copy-pasting something, start there.
How to use Crubit
Crubit allows you to call some Rust interfaces from C++. It supports
functions (including methods), structs, and even
enums as "opaque" objects. Crubit does not support advanced
features like generics or dynamic dispatch with dyn.
The rest of this document goes over how to create a Rust library that can be called from C++, and how to actually use it from C++. The quick summary is:
-
All
rust_librarytargets can receive C++ bindings. -
To use the bindings for a target
//path/to:example_crate, you must create a C++ rule exporting the bindings, usingcc_bindings_from_rust(name="any_name_here", crate=":example_crate"). -
The header name is the Rust target's label with a
.happended: to include the header for the Rust library//path/to:example_crate, you use#include "path/to/example_crate.h". -
The namespace name is the Rust target name, e.g.
example_crate. To change the namespace, usecc_bindings_from_rust_library_config, described below. -
To see the generated C++ API, right click the
"path/to/example_crate.h"include in Cider, and select "Go to Definition".NOTE: In some cases the generated file in Cider may be out of date. If it isn't refreshing, you can manually inspect the bindings using the workaround command in b/391395849.
Write a rust_library target
The first part of creating a library that can be used by Crubit is to write a
rust_library target. For example:
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
pub fn add_two_integers(x: i32, y: i32) -> i32 {
x + y
}
In the BUILD file, in addition to defining the rust_library, you should also
define the cc_bindings_from_rust target to make it easier to use from C++:
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load(
"@rules_rust//rust:defs.bzl",
"rust_library",
)
load(
"//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_rule.bzl",
"cc_bindings_from_rust",
)
load(
"//cc_bindings_from_rs/test/golden:golden_test.bzl",
"golden_test",
)
package(default_applicable_licenses = ["//:license"])
licenses(["notice"])
# This declares an "example_crate_cc_api" target that provides Crubit-generated
# C++ bindings for the Rust crate behind the `":example_crate"` target.
rust_library(
name = "example_crate",
srcs = ["example.rs"],
)
cc_bindings_from_rust(
name = "example_crate_cc_api",
crate = ":example_crate",
)
cc_binary(
name = "main",
srcs = ["main.cc"],
deps = [":example_crate_cc_api"],
)
golden_test(
name = "example_golden_test",
golden_h = "example_generated.h",
rust_library = "example_crate",
)
Example: If your Rust library is named //path/to:example_crate, then the C++
header file is "path/to/example_crate.h", and the C++ namespace is
example_crate by default.
Use a Rust library from C++
C++ build rules do not have a rust_deps parameter, so to depend on the C++
bindings for a target, they must depend on the cc_bindings_from_rust rule.
For example:
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load(
"@rules_rust//rust:defs.bzl",
"rust_library",
)
load(
"//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_rule.bzl",
"cc_bindings_from_rust",
)
load(
"//cc_bindings_from_rs/test/golden:golden_test.bzl",
"golden_test",
)
package(default_applicable_licenses = ["//:license"])
licenses(["notice"])
# This declares an "example_crate_cc_api" target that provides Crubit-generated
# C++ bindings for the Rust crate behind the `":example_crate"` target.
rust_library(
name = "example_crate",
srcs = ["example.rs"],
)
cc_bindings_from_rust(
name = "example_crate_cc_api",
crate = ":example_crate",
)
cc_binary(
name = "main",
srcs = ["main.cc"],
deps = [":example_crate_cc_api"],
)
golden_test(
name = "example_golden_test",
golden_h = "example_generated.h",
rust_library = "example_crate",
)
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cstdint>
#include <iostream>
// The generated bindings are in a header at the same path as the
// `example_crate` rust_library, with a `.h` suffix.
#include "examples/rust/function/example_crate.h"
int main(int argc, char* argv[]) {
// The generated bindings are in a namespace with the same name as the
// target crate:
int32_t sum = example_crate::add_two_integers(2, 2);
std::cout << "sum = " << sum << std::endl;
return 0;
}
NOTE: Other than for declaring the dependency, all other information about the
generated bindings comes from the actual rust_library rule. For example, the
#include for the above is #include "examples/rust/function/example_crate.h", not
example_crate_cc_api.h.
(Optional) Customize the generated C++ API
Give it a better namespace
The crate name might make a poor namespace. In addition, typically, multiple C++
headers and build targets share the same namespace. To customize the namespace
name, use cc_bindings_from_rust_library_config:
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load(
"@rules_rust//rust:defs.bzl",
"rust_library",
)
load("//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_library_config_aspect_hint.bzl", "cc_bindings_from_rust_library_config")
load(
"//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_rule.bzl",
"cc_bindings_from_rust",
)
load(
"//cc_bindings_from_rs/test/golden:golden_test.bzl",
"golden_test",
)
package(default_applicable_licenses = ["//:license"])
licenses(["notice"])
# This configuration places all names inside the namespace `my::library`, instead of the crate name.
cc_bindings_from_rust_library_config(
name = "custom_namespace",
namespace = "my::library",
)
# This declares an "example_crate_cc_api" target that provides Crubit-generated
# C++ bindings for the Rust crate behind the `":example_crate"` target.
rust_library(
name = "example_crate",
srcs = ["example.rs"],
aspect_hints = [":custom_namespace"],
)
cc_bindings_from_rust(
name = "example_crate_cc_api",
crate = ":example_crate",
)
cc_binary(
name = "main",
srcs = ["main.cc"],
deps = [":example_crate_cc_api"],
)
golden_test(
name = "example_golden_test",
golden_h = "example_generated.h",
rust_library = "example_crate",
)
Now, instead of the crate name, the generated bindings will use the namespace name you provided:
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cstdint>
#include <iostream>
#include "examples/rust/library_config/example_crate.h"
int main(int argc, char* argv[]) {
// The generated bindings are in `my::library`, not `example_crate`.
int32_t sum = my::library::add_two_integers(2, 2);
std::cout << "sum = " << sum << std::endl;
return 0;
}
Look at the generated bindings
There are two ways to look at the generated header file:
-
Click through the
#includein Cider. Given the following C++ code:#include "path/to/example_crate.h"If you right click the file path, and select "Go to Definition", you will be taken to a file starting with
// Automatically @generated C++ bindings. -
Run
bazel build //path/to:example_crate --config=crubit-genfiles, and openbazel-bin/path/to/example_crate.hin your text editor of choice.
Common Errors
Unsupported features
Some features are either unsupported, or else only supported with experimental feature flags (crubit.rs-features). In order to get bindings for a Rust interface, that interface must only use the subset of features currently supported.
For a particularly notable example, references are only supported as function parameters, and only in a subset of cases that we can prove does not add aliasing UB to C++ callers.
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:
- Use raw pointers instead of references, if this use of references falls into a case Crubit does not support.
- Hide unsupported types behind a wrapper type. For example, a
Vec<T>is not supported by Crubit, butpub struct MyStruct(Vec<i32>);is.