blob: f1784447b1558929783722aeb1154e67dedc7329 [file] [view] [edit]
# 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 {#introduction}
Crubit allows you to call some C++ interfaces from Rust. It supports
[functions](functions.md), [classes and structs](classes_and_structs.md), and
[enums](enums.md), 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:
1. Define a `rust_api_from_cpp` target in the same `BUILD` file as your
`cc_library`.
2. Add the generated `.hint` target (e.g. `:<name_of_rust_target>.hint`) to the
`aspect_hints` of your `cc_library`.
3. Depend on the `rust_api_from_cpp` target in the `deps` of your Rust target.
In Rust, the crate is named after the `cc_library`, not the
`rust_api_from_cpp` target. See [Crate name](#crate_name).
The bindings can be previewed by building the `cc_library` with
`--config=crubit-genfiles`. See [Look at the generated bindings](#examine).
```sh
$ bazel build --config=crubit-genfiles //path/to:my_cc_library
```
### Write a `cc_library` target {#cc_library}
The first part of creating a library that can be used by Crubit is to write a
`cc_library` target. For example:
```live-snippet
cs/file: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:
```live-snippet
cs/file:examples/cpp/function/BUILD symbol:example_lib_broken
```
### Enable Crubit on a target {#enable}
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 aspects[^aspects] 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`:
```live-snippet
cs/file:examples/cpp/function/BUILD symbol:\bexample_lib\b
```
```live-snippet
cs/file:examples/cpp/function/BUILD symbol:\bexample_lib_rust\b
```
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 {#examine}
To examine the generated Rust bindings for a C++ library, build the `cc_library`
target with `--config=crubit-genfiles`:
```sh
$ 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:
1. The supporting C++ code to glue Rust and C++ together. (The `.cc` file.)
2. The public Rust interface. (The `.rs` file.)
3. Supporting internal implementation details. (The `.json` file.)
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:
```live-snippet
cs/file:examples/cpp/function/example_generated.rs
```
#### When something is missing {#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:
```rust
// 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](customizing.md).
### Use a C++ library from Rust {#use}
To depend on a C++ library from Rust, add the corresponding `rust_api_from_cpp`
target to your Rust target's `deps`:
```live-snippet
cs/file:examples/cpp/function/BUILD symbol:main
```
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:
```live-snippet
cs/file:examples/cpp/function/main.rs
```
#### Crate name {#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 `n` prefix: `3d_math` becomes
`n3d_math`.
* A target named `core` becomes `core_` plus the last directory of its
package, so that it doesn't shadow Rust's `core` crate: `//foo/bar:core`
becomes `core_bar`.
## Common Errors {#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 a `std::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!`**](inline_cpp.md) to
allow embedding arbitrary C++ code within a Rust target using the
`rust_library_with_embedded_cpp` Bazel rule.
[^aspects]: 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 `A` returns a
struct from target `B`, we know that the bindings for `A` will depend on the
bindings for `B`. Because Crubit is an aspect, it already knows the name of
the bindings for `B`: it's simply the Crubit aspect on `B`!
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_library` doesn'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.