Back to Chromium

Rust FFI

docs/rust/ffi.md

151.0.7909.08.0 KB
Original Source

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. For introductory guidance, please see the cxx chapter 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 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. 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 (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 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 or 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. 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: 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.

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. 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 bridges, 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 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, but some idioms are especially useful in an FFI context:

  • Use From (or TryFrom) to convert between types; it's especially well-suited to converting between types that are logically equivalent, such as FFI types like ffi::ColorType and third-party crate types like png::ColorType.

  • Use the ? operator alongside the Option or 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 and here. This example avoids having to come up with a separate name by using an anonymous function.

  • Use the let Ok(foo) = ... else { ... } syntax to handle errors when you don't always want to return if you see a None. See an example here.