blob: 20b6adb558921886e768c95df9ece193d62c4793 [file] [view]
# Rust bindings for C++ classes and structs
A C++ `class` or `struct` is mapped to a Rust `struct` with the same fields. If
any subobject of the class cannot be represented in Rust, the class itself will
still have bindings, but
[the relevant subobject will be private](#opaque_fields).
## Example
Given the following C++ header:
```live-snippet
cs/file:examples/cpp/trivial_struct/example.h class:Position
```
Crubit will generate a struct with the same layout:
```live-snippet
cs/file:examples/cpp/trivial_struct/example_generated.rs class:Position
```
Which can be used like this:
```live-snippet
cs/file:examples/cpp/trivial_struct/main.rs function:main
```
For an example of a Rust-movable class with a destructor, see
[examples/cpp/trivial_abi_struct/](https://github.com/google/crubit/tree/main/examples/cpp/trivial_abi_struct/).
## Fields {#fields}
The fields on the Rust struct type are the corresponding Rust types:
* If the C++ field has [primitive type](../types/primitive.md), then the Rust
field uses the corresponding Rust type.
* Similarly, if the C++ field has [pointer type](../types/pointer.md), then
the Rust field has the corresponding Rust pointer type.
* If the field has a user-defined type, such as a
[class type](classes_and_structs.md) or [enum](enums.md), then the bindings
for the function use the bindings for that type.
### Unsupported fields {#opaque_fields}
Subobjects that do not receive bindings are made private, and replaced with an
opaque blob of `[MaybeUninit<u8>; N]`, as well as a comment in the generated
source code explaining why the subobject could not receive bindings. For
example, base class subobjects are not exposed as fields, so the space of the
object occupied by a base class will instead be this opaque blob of bytes, with
no comment. (See [Base classes](#inheritance) for what you can do with base
classes instead.)
Specifically, the following subobjects are hidden and replaced with opaque
blobs:
* Base class subobjects
* Non-`public` fields (`private` or `protected` fields)
* Fields that have nontrivial destructors
* Fields whose type does not have bindings
* Fields that have any unrecognized attribute, including `no_unique_address`
A Rust struct with opaque blobs is ABI-incompatible with the C++ struct or class
that it corresponds to. As a consequence, if the struct is used for FFI outside
of Crubit, it should not be passed by value. Within Crubit, it can't be passed
by value in [function pointers](../types/pointer.md#function), but can otherwise
be used as normal.
## Static data members {#static_members}
Public static data members get bindings: compile-time integer and `bool`
constants become associated `const`s of the Rust struct, and other static data
members become Rust `static`s. See [Static data members](static_data_members.md)
for details.
<span id="trivially_relocatable"></span>
## Rust-movable classes {#rust_movable}
The easiest C++ classes to work with are "Rust-movable", meaning they support
being relocated in memory using `memcpy` without running the move constructor.
When passed and returned by value, Rust-movable classes will use the direct
type. For example, `T Identity(T)` becomes `pub fn Identity(_: T) -> T`.
If it is non-Rust-movable, then Crubit will instead use in-place initialization
types, `Ctor`, and requires a bit of syntactic overhead when moving objects
around. The same function might instead become `pub fn Foo(_: Ctor![T]) ->
Ctor![T]`.
(For an introduction to `Ctor![T]`, see
crubit.rs/types/non_rust_movable/intro_short.)
Types are considered Rust-movable by default, meaning they can be relocated
using `memcpy`. If the type defines a destructor or copy/move constructor, then
it requires a special annotation to be considered Rust-movable:
[`ABSL_ATTRIBUTE_TRIVIAL_ABI`](https://github.com/abseil/abseil-cpp/blob/master/absl/base/attributes.h#:~:text=ABSL_ATTRIBUTE_TRIVIAL_ABI).
If it has a non-Rust-movable field or base class, then it is not Rust-movable.
It can be worth putting some effort into designing the type to be Rust-movable.
crubit.rs/cpp/cookbook#rust_movable describes, in more detail, how to go about
this.
Some examples of Rust-movable types:
* any primitive type (integers, character types, floats, etc.)
* raw pointers
* `string_view`
* [`struct tm`](https://en.cppreference.com/w/cpp/chrono/c/tm), or any other
type in the C standard library
* `unique_ptr` and `shared_ptr`, in the Clang unstable ABI.
* `absl::Status`
Some examples of types that are **not** Rust-movable:
* (For now) `std::string`, `std::vector`, and other nontrivial standard
library types.
* (For now) `absl::flat_hash_map`, `absl::AnyInvocable`, and other nontrivial
types used throughout the C++ ecosystem, even outside the standard library.
* `absl::Mutex`, `absl::Notification`, and other non-movable types.
## Base classes {#inheritance}
Rust has no inheritance, so a derived class and its base classes become
unrelated Rust structs. The base class subobject is an
[opaque blob](#opaque_fields), so base class fields aren't accessible through
the derived struct. With `oo_casting`, Rust code can
[upcast](#inherited_methods) to the base class and read the fields there.
### Inherited methods {#inherited_methods}
By default, methods that a class inherits from its base classes are **not** part
of the derived class's bindings, and no comment is emitted about them. They
still exist on the base class's bindings.
```c++
struct Base {
bool IsValid() const;
};
struct Derived : Base {};
```
Here, `Base::IsValid()` has bindings, but `Derived` has no `IsValid()` method in
Rust.
To get inherited methods, add `//features:oo_casting` to the
`aspect_hints` of the `cc_library` that defines the derived class. This is an
[experimental](../overview/status.md) feature. With it:
* The derived struct gets the public methods, including static methods, of its
public base classes, so `derived.IsValid()` works in Rust. This only covers
base classes defined in the same `cc_library` as the derived class, and
leaves out methods whose name comes from more than one base class.
* The derived struct implements `oops::Inherits<Base>` for each public base
class, including base classes from other `cc_library` targets. Rust code can
then use the `oops::Upcast` trait to convert a `&Derived`, `*const Derived`,
or `*mut Derived` to the base class type. For example, to call a method of a
base class in another `cc_library`, upcast first:
```rust
use oops::Upcast as _;
// The type annotation is required.
let base: &Base = unsafe { derived.upcast() };
```
For a `virtual` base class, use `oops::VirtualUpcast` on a raw pointer
instead. Rust code that uses these traits depends on
`//support:oops`.
The tests in
[`rs_bindings_from_cc/test/struct/inheritance/`](/rs_bindings_from_cc/test/struct/inheritance/)
show both.
Without `oo_casting`, add a method to the derived class in C++ that forwards to
the base class method, or a function that returns the object as a pointer to its
base class.
`using Base::Method;` declarations in the derived class don't get bindings, and
produce a "Function aliases are not yet supported" error comment. With
`oo_casting`, the method is usually inherited anyway. It's missing only if the
`using` declaration makes a method of a non-public base class accessible, or
brings back a base class method that a derived class method of the same name
hides.
### Virtual methods {#virtual}
Calling a C++ virtual method from Rust works like calling any other method, and
dispatches virtually. This includes pure virtual methods. Because a class with
virtual methods is not [Rust-movable](#rust_movable), non-`const` methods take
`self: Pin<&mut Self>`.
An abstract class (one with pure virtual methods that aren't overridden) gets
bindings for its methods but no constructor, so Rust code works with it through
references and pointers to objects created elsewhere. A subclass that overrides
the methods gets bindings for its overrides and can be constructed. The methods
it doesn't override are [inherited methods](#inherited_methods).
Overriding a C++ virtual method in Rust, or otherwise implementing a C++
interface in Rust, is not supported. The workaround is a C++ subclass that
forwards to a Rust type; see
[`examples/cpp/virtual/`](/examples/cpp/virtual/).
## Attributes {#attributes}
Crubit does not support most attributes on structs and their fields. If a struct
is marked using any attribute other than alignment or
`ABSL_ATTRIBUTE_TRIVIAL_ABI`, it will not receive bindings. If a field is marked
using any other attribute, it will be replaced with a private opaque blob.