Support shared struct with C++ source of truth
Author: CAD97Created May 10, 2021Updated May 25, 2026
I want to be able to write the following or equivalent:
#[cxx::bridge]
fn ffi {
struct FVector {
pub X: f32,
pub Y: f32,
pub Z: f32,
}
extern "C++" {
include!("Vector.h");
type FVector;
}
}and it have it expand to the the following or equivalent:
// Rust
#[repr(C)]
struct FVector {
pub X: f32,
pub Y: f32,
pub Z: f32,
}// Cxx
#include "Vector.h"
#include <cstdint>
#include <cstddef>
#include <type_traits>
#include <utility>
static_assert(::std::is_class<FVector>::value, "expected class");
static_assert(sizeof(FVector) == 12, "incorrect size");
static_assert(offsetof(FVector, X) == 0, "disagrees with the value in #[cxx::bridge]");
static_assert(::std::is_same<decltype(::std::declval<FVector>().X), float>::value, "disagrees with the value in #[cxx::bridge]");
static_assert(offsetof(FVector, Y) == 4, "disagrees with the value in #[cxx::bridge]");
static_assert(::std::is_same<decltype(::std::declval<FVector>().Y), float>::value, "disagrees with the value in #[cxx::bridge]");
static_assert(offsetof(FVector, Z) == 8, "disagrees with the value in #[cxx::bridge]");
static_assert(::std::is_same<decltype(::std::declval<FVector>().Z), float>::value, "disagrees with the value in #[cxx::bridge]");This uses only C++11 functionality as written. The only member types supported would be primitive types or (potentially) shared types defined in the same bridge module. This requires knowledge of the repr(C) layout algorithm to assert that it is correct; for this use case we can use the simple definition:
fn repr_c(field_layouts: Vec<Layout>) -> Result<StructLayout> {
let mut layout = Layout::new::<()>();
let mut fields = vec![];
for field in field_layouts {
let (extended, offset) = layout.extend(field_layout)?;
layout = extended;
offsets.push(FieldLayout { layout: field_layout, offset });
}
layout.pad_to_align();
StructLayout { layout, fields }
}For extra paranoia we could also emit assertions that our understanding is correct on the Rust side as well.
Preprocessor macros to make doing so manually easier#define PREPROCESSOR_TO_STRING(x) PREPROCESSOR_TO_STRING_INNER(x)
#define PREPROCESSOR_TO_STRING_INNER(x) #x
#define ASSERT_TYPE_SIZE(Type, Size) \
static_assert( \
sizeof(Type) == Size, \
PREPROCESSOR_TO_STRING(Type) " does not have expected size " PREPROCESSOR_TO_STRING(Size));
#define ASSERT_TYPE_MEMBER(Type, Offset, MemberType, Member, ...) \
static_assert( \
offsetof(Type, Member) == Offset, \
PREPROCESSOR_TO_STRING(Type) "::" PREPROCESSOR_TO_STRING(Member) " is not at expected offset " PREPROCESSOR_TO_STRING(Offset)); \
static_assert( \
std::is_same<decltype(std::declval<Type>().Member), MemberType>::value, \
PREPROCESSOR_TO_STRING(Type) "::" PREPROCESSOR_TO_STRING(Member) " is not expected type " PREPROCESSOR_TO_STRING(MemberType));
ASSERT_TYPE_SIZE(FVector, 12);
ASSERT_TYPE_MEMBER(FVector, 0, float, X);
ASSERT_TYPE_MEMBER(FVector, 4, float, Y);
ASSERT_TYPE_MEMBER(FVector, 8, float, Z);Source: dtolnay/cxx