blob: 2e5603823bcb26f084015c4b9453fecf4126b06d [file] [view]
# Rust FFI
This document provide guidance for C++/Rust interoperation using FFI (foreign
function interfaces).
[TOC]
## Which tool should I use?
For interoperating with **C++ code**, we recommend the
[the `cxx` crate](https://cxx.rs/).
For introductory guidance, please see
[the `cxx` chapter](https://google.github.io/comprehensive-rust/chromium/interoperability-with-cpp.html)
in the Chromium section of the Comprehensive Rust course.
If you only need to use a **C APIs** from Rust (and not the other direction),
you can use the [`bindgen`](https://rust-lang.github.io/rust-bindgen/) tool.
Bindgen will automatically create Rust equivalents of the input C code, which
you can then use from your own Rust code. For example, this project uses bindgen to
[import the Mojo C API](https://source.chromium.org/chromium/chromium/src/+/main:mojo/public/rust/c_mojo_api/BUILD.gn;l=30;drc=84d3affd8824ebaa8ab04d0f20bcebb64b484704).
For documentation of various options, see `//build/rust/rust_bindgen.gni`.
Bindgen can also be used to import C++ headers, but it only supports a limited
subset of the language.
**In the future**, we intend to shift from `cxx` to
[Crubit](https://github.com/google/crubit/) (tracked in <https://crbug.com/470466915>). However, Crubit is still transitioning to open-source, and is not
yet officially supported in Chromium.
See [`crubit.md`](crubit.md) for some in-progress notes on the current
implementation and how to use it.
Chromium **does not support any other FFI tools** (e.g.
[`cbindgen`](https://github.com/mozilla/cbindgen) or
[`zngur`](https://github.com/HKalbasi/zngur)), and we do not plan to support
them in the future.
## Safety considerations
All FFI code is `unsafe` by definition, since the Rust compiler cannot enforce
its rules on non-Rust code. This means that when working with FFI, you must be
careful to understand exactly what behaviors are possible. Make sure you're
familiar with our [safety guidelines](docs/rust/unsafe.md#how-to-use-unsafe).
So long as you're consistent about writing and following safety comments, you
can avoid most problems.
* **Opaque types**: When sharing a type from C++ to Rust using a `cxx::bridge`,
it is treated as opaque, zero-sized placeholder type from the perspective of
the Rust compiler. This means that it must always be behind an indirection (a
pointer, reference, box, etc.), and the Rust compiler makes no assumptions
about what it points to.
* In particular, is it not fundamentally unsound for C++ to mutate an opaque
value while Rust has a reference to it, although you must still be cautious
of e.g. race conditions.
* **[Shared types](https://cxx.rs/shared.html)**: Rust has visibility into these
types, so you must ensure C++ obeys all aliasing rules, e.g. not mutating the
value while Rust has a reference to it.
* **Widespread types**: Some types are widely used throughout Chromium's C++
codebase, and auditing every usage for safety is infeasible. In such cases,
you may assume that C++ does not already contain undefined behavior.
* For example, you may assume that mutations to the
[static per-process `CommandLine` variable](https://source.chromium.org/chromium/chromium/src/+/main:base/command_line.cc;l=38;drc=5496fbe366e6796c5b549c65a2cb267b5c933e35)
are not already racy, even though there isn't specific code to enforce that.
## `cxx` guidance
The `cxx` crate is the current standard for C++/Rust interop in Chromium, but it
has some rough edges that we hope will eventually be smoothed over with the
[adoption of Crubit](https://crbug.com/470466915). This section provides advice
for working with `cxx` in the meantime.
### Best practices
* Maintain binding declarations in a **single** `#[cxx::bridge]` declaration.
`cxx` supports reusing types across multiple `bridge`s, but there are some
rough edges.
* Generate C++ side of bindings into a project-specific or crate-specific
`namespace`. For example: `#[cxx::bridge(namespace = "some_cpp_namespace")]`.
* Prefer to call existing C++ functions when possible. When you cannot (e.g.
because it involves a type that `cxx` can't translate), create wrapper
functions in a C++ shim file.
* By convention, these files end in `_shim.h` (or `.cc`).
* [Think carefully](#safety-considerations) about if and how data can be
shared by C++ and Rust at the same time, as this is a major source of
potential unsafety.
### Suggestions
These are not necessarily best practices, but take them into consideration when
designing your bridges.
* If you're not using a wrapper type, leverage `cxx`'s [attributes] to generate
more idiomatic code:
* The `Self` attribute can attach static functions to existing types.
* The `rust_name` attribute is useful for translating between naming
conventions.
* If you're exposing a function without safety requirements (e.g. because it
doesn't manipulate memory), you mark it as safe by annotating the entire
`extern "C++"` block as `unsafe`.
* A good rule of thumb is to do this after you try to write a safety comment
and can't come up with any way for things to go wrong.
## General Tips and Tricks
In general, you should follow
[idomatic Rust style](https://doc.rust-lang.org/style-guide/), but some idioms
are especially useful in an FFI context:
* Use [`From`](https://doc.rust-lang.org/std/convert/trait.From.html) (or
[`TryFrom`](https://doc.rust-lang.org/std/convert/trait.TryFrom.html)) to
convert between types; it's especially well-suited to converting between
types that are logically equivalent, such as FFI types like
[`ffi::ColorType`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/experimental/rust_png/ffi/FFI.rs;l=20-27;drc=70253db1ecfe261003756f0d81ae30929cc77ee4)
and third-party crate types like
[`png::ColorType`](https://docs.rs/png/0.17.6/png/enum.ColorType.html).
* See an [example trait implementation](https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/experimental/rust_png/ffi/FFI.rs;l=221-231;drc=70253db1ecfe261003756f0d81ae30929cc77ee4)
and an example of [using the conversion](https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/experimental/rust_png/ffi/FFI.rs;l=651;drc=70253db1ecfe261003756f0d81ae30929cc77ee4)
as `foo.into()`.
* Note that when implementing the conversion for types defined in other crates,
you may need to work around the
[orphan rule](https://doc.rust-lang.org/reference/items/implementations.html#r-items.impl.trait.orphan-rule)
by implementing
[`Into`](https://doc.rust-lang.org/std/convert/trait.Into.html)
(or
[`TryInto`](https://doc.rust-lang.org/std/convert/trait.TryInto.html))
trait instead.
* Use the [? operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.try)
alongside the [Option](https://doc.rust-lang.org/std/option/) or
[Result](https://doc.rust-lang.org/std/result/) types to check for errors.
* When used in the FFI layer, this may require splitting some functions
into (a) one that returns `Result<T, E>` and uses `?` sugar,
and (b) one that translates `Result<T, E>` into FFI-friendly
status.
* There are some examples
[here](https://source.chromium.org/chromium/chromium/src/+/main:components/user_data_importer/utility/zip_ffi_glue.rs;l=297;drc=33f81e080c4c06d18880ec04832511bda3929972)
and
[here](https://source.chromium.org/chromium/chromium/src/+/main:components/user_data_importer/utility/zip_ffi_glue.rs;l=421-427;drc=33f81e080c4c06d18880ec04832511bda3929972).
[This example](https://chromium-review.googlesource.com/c/chromium/src/+/6733098/18/components/user_data_importer/utility/zip_ffi_glue.rs#484)
avoids having to come up with a separate name by using an anonymous function.
* Use the [`let Ok(foo) = ... else { ... }`](https://doc.rust-lang.org/rust-by-example/flow_control/let_else.html) syntax to handle errors when you
don't always want to return if you see a `None`. See
[an example here](https://source.chromium.org/chromium/chromium/src/+/main:components/user_data_importer/utility/zip_ffi_glue.rs;l=328-333;drc=33f81e080c4c06d18880ec04832511bda3929972).