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.
Example
Given the following C++ header:
// 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
#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_CPP_TRIVIAL_STRUCT_EXAMPLE_H_
#define THIRD_PARTY_CRUBIT_EXAMPLES_CPP_TRIVIAL_STRUCT_EXAMPLE_H_
struct Position {
int x;
int y;
};
#endif // THIRD_PARTY_CRUBIT_EXAMPLES_CPP_TRIVIAL_STRUCT_EXAMPLE_H_
Crubit will generate a struct with the same layout:
// 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
// Automatically @generated Rust bindings for the following C++ target:
// //examples/cpp/trivial_struct:example_lib
// Features: custom_ffi_types, non_unpin_ctor, std_unique_ptr, std_vector, supported
#![rustfmt::skip]
#![feature(allocator_api, cfg_sanitize, custom_inner_attributes, negative_impls)]
#![allow(stable_features)]
#![no_std]
#![allow(improper_ctypes)]
#![allow(nonstandard_style)]
#![allow(dead_code, unused_mut)]
#![deny(warnings)]
/// Generated from: examples/cpp/trivial_struct/example.h;l=8
#[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)]
#[repr(C)]
///CRUBIT_ANNOTATE: cpp_type=Position
pub struct Position {
pub x: ::ffi_11::c_int,
pub y: ::ffi_11::c_int,
}
impl !Send for Position {}
impl !Sync for Position {}
unsafe impl ::cxx::ExternType for Position {
type Id = ::cxx::type_id!("Position");
type Kind = ::cxx::kind::Trivial;
}
/// Generated from: examples/cpp/trivial_struct/example.h;l=8
impl Default for Position {
#[inline(always)]
fn default() -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN8PositionC1Ev(&raw mut tmp as *mut _);
tmp.assume_init()
}
}
}
mod detail {
#[allow(unused_imports)]
use super::*;
unsafe extern "C" {
pub(crate) unsafe fn __rust_thunk___ZN8PositionC1Ev(__this: *mut ::core::ffi::c_void);
}
}
const _: () = {
assert!(::core::mem::size_of::<crate::Position>() == 8);
assert!(::core::mem::align_of::<crate::Position>() == 4);
static_assertions::assert_impl_all!(crate::Position: Copy,Clone);
static_assertions::assert_not_impl_any!(crate::Position: Drop);
assert!(::core::mem::offset_of!(crate::Position, x) == 0);
assert!(::core::mem::offset_of!(crate::Position, y) == 4);
};
For an example of a Rust-movable class with a destructor, see examples/cpp/trivial_abi_struct/.
Fields
The fields on the Rust struct type are the corresponding Rust types:
- If the C++ field has primitive type, then the Rust field uses the corresponding Rust type.
- Similarly, if the C++ field has pointer type, then the Rust field has the corresponding Rust pointer type.
- If the field has a user-defined type, such as a class type or enum, then the bindings for the function use the bindings for that type.
Unsupported 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, since inheritance is not supported, the space of the object occupied by
a base class will instead be this opaque blob of bytes.
Specifically, the following subobjects are hidden and replaced with opaque blobs:
- Base class subobjects
- Non-
publicfields (privateorprotectedfields) - 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, but can otherwise be used as normal.
Rust-movable classes
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.
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_viewstruct tm, or any other type in the C standard libraryunique_ptrandshared_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.
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.