Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions docs/errors/bridge_field.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<!-- <internal link> -->

# Bridge types as struct fields

## Overview

Crubit does not support exposing struct or union fields with bridge types.

Bridge types are types that are converted at runtime between C++ and Rust
(`std::optional` mapping to `Option`), but have different underlying
representations. (See crubit.rs/types)

If a field is a bridge type, this conversion is impossible: field accesses do
not run any conversion logic, and the field is required to have identical
representation across both languages. As a result, the field will not have the
bridge type conversion applied, but instead will be replaced by an opaque blob
of bytes.

## Example

Consider the following C++ struct:

```c++
#include <optional>
#include <cstdint>

struct Config {
std::optional<int> foo;
};
```

Crubit will treat the `foo` field of `Config` as an opaque blob of bytes that
cannot be accessed directly from Rust.

## Workaround: Add getter and setter methods {#workaround}

To access or modify the field from Rust, you can add getter and setter methods.
Crubit *can* generate bindings for functions that accept or return bridge types
by value.

For example:

```c++
#include <optional>

struct Config {
std::optional<int> foo;

// Getter returning by value (mapped to Option<i32> in Rust)
std::optional<int> get_foo() const { return foo; }

// Setter accepting by value
void set_foo(std::optional<int> val) { foo = val; }
};
```

Then in Rust, you can use these methods:

```rust
let mut config = Config::default();
config.set_foo(Some(42));
assert_eq!(config.get_foo(), Some(42));
```

See crubit.rs/cpp/best_practices#bridging for more information.
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ fn get_field_rs_type_kind_for_layout(
}

if type_kind.is_bridge_type() {
bail!("Bridge-by-value types are not supported in struct fields.")
bail!("crubit.rs/errors/bridge_field: '{}' is a bridge type, but fields must be layout compatible between Rust and C++.",
type_kind.display(db))
}

for target in
Expand Down
4 changes: 4 additions & 0 deletions rs_bindings_from_cc/test/golden/composable_bridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ struct
// clang-format on
CppStruct {};

struct StructWithBridgeField {
CppStruct bridge_field;
};

CppStruct ReturnCppStruct();

void TakeCppStruct(CppStruct);
Expand Down
34 changes: 34 additions & 0 deletions rs_bindings_from_cc/test/golden/composable_bridging_rs_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@
// order for the generated code to properly compile. This example just serves to
// illustrate what the generated code will look like.

#[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)]
#[repr(C)]
///CRUBIT_ANNOTATE: cpp_type=StructWithBridgeField
pub struct StructWithBridgeField {
/// Reason for representing this field as a blob of bytes:
/// crubit.rs/errors/bridge_field: 'crate::RustStruct' is a bridge type, but fields must be layout compatible between Rust and C++.
pub(crate) bridge_field: [::core::mem::MaybeUninit<u8>; 1],
}
impl !Send for StructWithBridgeField {}
impl !Sync for StructWithBridgeField {}
unsafe impl ::cxx::ExternType for StructWithBridgeField {
type Id = ::cxx::type_id!("StructWithBridgeField");
type Kind = ::cxx::kind::Trivial;
}

impl Default for StructWithBridgeField {
#[inline(always)]
fn default() -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN21StructWithBridgeFieldC1Ev(&raw mut tmp as *mut _);
tmp.assume_init()
}
}
}

#[inline(always)]
pub fn ReturnCppStruct() -> crate::RustStruct {
unsafe {
Expand Down Expand Up @@ -568,6 +594,9 @@ mod detail {
#[allow(unused_imports)]
use super::*;
unsafe extern "C" {
pub(crate) unsafe fn __rust_thunk___ZN21StructWithBridgeFieldC1Ev(
__this: *mut ::core::ffi::c_void,
);
pub(crate) unsafe fn __rust_thunk___Z15ReturnCppStructv(
__return_abi_buffer: *mut ::core::ffi::c_uchar,
);
Expand Down Expand Up @@ -617,6 +646,11 @@ mod detail {
}

const _: () = {
assert!(::core::mem::size_of::<crate::StructWithBridgeField>() == 1);
assert!(::core::mem::align_of::<crate::StructWithBridgeField>() == 1);
static_assertions::assert_impl_all!(crate::StructWithBridgeField: Copy,Clone);
static_assertions::assert_not_impl_any!(crate::StructWithBridgeField: Drop);
assert!(::core::mem::offset_of!(crate::StructWithBridgeField, bridge_field) == 0);
assert!(::core::mem::size_of::<crate::Vec3>() == 12);
assert!(::core::mem::align_of::<crate::Vec3>() == 4);
static_assertions::assert_impl_all!(crate::Vec3: Copy,Clone);
Expand Down
10 changes: 10 additions & 0 deletions rs_bindings_from_cc/test/golden/composable_bridging_rs_api_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wthread-safety-analysis"

static_assert(sizeof(struct StructWithBridgeField) == 1);
static_assert(alignof(struct StructWithBridgeField) == 1);
static_assert(CRUBIT_OFFSET_OF(bridge_field, struct StructWithBridgeField) ==
0);

extern "C" void __rust_thunk___ZN21StructWithBridgeFieldC1Ev(
struct StructWithBridgeField* __this) {
crubit::construct_at(__this);
}

extern "C" void __rust_thunk___Z15ReturnCppStructv(
unsigned char* __return_abi_buffer) {
::crubit::Encoder __return_encoder(::crubit::CppStructAbi::kSize,
Expand Down
Loading