C++ bindings for Rust structs
A Rust struct is mapped to a C++ class/struct with the same fields. If any
field cannot be represented in C++, the struct itself will still have bindings,
but the relevant field will be private.
To receive C++ bindings, the struct must be movable in C++. See
Movable Types.
Example
Given the following Rust module:
// 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
#[derive(Default, Clone)]
pub struct Struct {
pub a: i32,
}
Crubit will generate the following bindings:
// 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 C++ bindings for the following Rust crate:
// example_crate_golden
// Features: fmt, supported, types
// clang-format off
#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_RUST_STRUCT_EXAMPLE_CRATE_GOLDEN
#define THIRD_PARTY_CRUBIT_EXAMPLES_RUST_STRUCT_EXAMPLE_CRATE_GOLDEN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
#pragma clang diagnostic ignored "-Wunused-private-field"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#pragma clang diagnostic ignored "-Wignored-attributes"
#include "support/annotations_internal.h"
#include "support/internal/slot.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
namespace example_crate {
// Generated from:
// examples/rust/struct/example.rs;l=6
struct CRUBIT_INTERNAL_RUST_TYPE(":: example_crate_golden :: Struct") alignas(4)
[[clang::trivial_abi]] Struct final {
public:
// Default::default
Struct();
// No custom `Drop` impl and no custom "drop glue" required
~Struct() = default;
Struct(Struct&&) = default;
Struct& operator=(Struct&&) = default;
// Clone::clone
Struct(const Struct&);
// Clone::clone_from
::example_crate::Struct& operator=(const Struct&);
Struct(::crubit::UnsafeRelocateTag, Struct&& value) {
::std::memcpy(this, &value, sizeof(value));
}
union {
// Generated from:
// examples/rust/struct/example.rs;l=7
::std::int32_t a;
};
private:
static void __crubit_field_offset_assertions();
};
static_assert(
sizeof(Struct) == 4,
"Verify that ADT layout didn't change since this header got generated");
static_assert(
alignof(Struct) == 4,
"Verify that ADT layout didn't change since this header got generated");
namespace __crubit_internal {
extern "C" void __crubit_thunk_default(::example_crate::Struct* __ret_ptr);
}
inline ::example_crate::Struct::Struct() {
__crubit_internal::__crubit_thunk_default(this);
}
static_assert(::std::is_trivially_destructible_v<Struct>);
static_assert(
::std::is_trivially_move_constructible_v<::example_crate::Struct>);
static_assert(::std::is_trivially_move_assignable_v<::example_crate::Struct>);
namespace __crubit_internal {
extern "C" void __crubit_thunk_clone(::example_crate::Struct const&,
::example_crate::Struct* __ret_ptr);
}
namespace __crubit_internal {
extern "C" void __crubit_thunk_clone_ufrom(::example_crate::Struct&,
::example_crate::Struct const&);
}
inline ::example_crate::Struct::Struct(const Struct& other) {
__crubit_internal::__crubit_thunk_clone(other, this);
}
inline ::example_crate::Struct& ::example_crate::Struct::operator=(
const Struct& other) {
if (this != &other) {
__crubit_internal::__crubit_thunk_clone_ufrom(*this, other);
}
return *this;
}
inline void Struct::__crubit_field_offset_assertions() {
static_assert(0 == offsetof(Struct, a));
}
} // namespace example_crate
#pragma clang diagnostic pop
#endif // THIRD_PARTY_CRUBIT_EXAMPLES_RUST_STRUCT_EXAMPLE_CRATE_GOLDEN
Fields
The fields on the C++ class are the corresponding Rust types:
- If the Rust field has primitive type, then the C++ field uses the corresponding C++ type.
- Similarly, if the Rust field has pointer type, then the C++ field has the corresponding C++ pointer type.
- If the field has a user-defined type, such as a struct or enum, then the bindings for the function use the bindings for that type.
Unsupported fields
Fields that do not receive bindings are made private, and replaced with an
opaque blob of maybe-uninitialized bytes, as well as a comment in the generated
source code explaining why the field could not receive bindings. For example,
since String is not supported, the space of the object occupied by a String
field will instead be this opaque blob of bytes:
#![allow(unused)]
fn main() {
// Rust: `my_field` is some unsupported type, such as `String`
pub my_field: String,
}
// C++: `my_field` becomes `private`, and its type is replaced by bytes.
private: unsigned char my_field[24]
Specifically, the following subobjects are hidden and replaced with opaque blobs:
- Non-public fields (
privateorpub(...)fields). - Fields that implement
Drop. - Fields whose type does not have bindings.
- Fields that have an unrecognized or unsupported attribute.
C++ movable
To receive C++ bindings, the struct must be movable in C++. See
Movable Types.
CRUBIT_INTERNAL_RUST_TYPE annotation
You may notice that the generated C++ structs are annotated with the
CRUBIT_INTERNAL_RUST_TYPE macro. This annotation instructs Crubit
(specifically rs_bindings_from_cc) to disable automated bindings for this C++
type, and instead map all C++ uses of the type back to the existing Rust type.
This ensures that a Rust struct passed to C++ and then back to Rust resolves to
the original Rust type rather than a newly generated one.
While Crubit generates this annotation automatically for Rust-to-C++ bindings, you can also apply it manually on your own C++ types if you want them to map to an existing Rust type:
struct CRUBIT_INTERNAL_RUST_TYPE("char") char_ {
std::uint32_t c;
};
Template Arguments and Interpolation
For C++ templates, you can use {} interpolation syntax within the Rust type
string to substitute template arguments:
template <typename T>
struct CRUBIT_INTERNAL_RUST_TYPE("RustType<{}>", T) CppType {
T* value;
};
This ensures that a C++ instantiation like CppType<int> maps correctly to
RustType<i32> in Rust.
Importantly, this interpolation syntax allows you to express Rust generic parameters that have no direct C++ equivalent, such as lifetimes or default generic arguments. For example:
template <typename T>
struct CRUBIT_INTERNAL_RUST_TYPE("RustType<'static, {}>", T) CppType {
T* value;
};
Const generics arguments can also be provided with
crubit::rust_type::Const<N>:
template <typename T>
struct CRUBIT_INTERNAL_RUST_TYPE(
"RustType<'static, {}, {}>",
T,
crubit::rust_type::Const<123>,
) CppType {
T* value;
};