diff --git a/docs/errors/bridge_field.md b/docs/errors/bridge_field.md new file mode 100644 index 000000000..b74c44fc7 --- /dev/null +++ b/docs/errors/bridge_field.md @@ -0,0 +1,65 @@ + + +# 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 +#include + +struct Config { + std::optional 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 + +struct Config { + std::optional foo; + + // Getter returning by value (mapped to Option in Rust) + std::optional get_foo() const { return foo; } + + // Setter accepting by value + void set_foo(std::optional 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. diff --git a/rs_bindings_from_cc/generate_bindings/generate_struct_and_union.rs b/rs_bindings_from_cc/generate_bindings/generate_struct_and_union.rs index 469bdea20..6c3dbfb6f 100644 --- a/rs_bindings_from_cc/generate_bindings/generate_struct_and_union.rs +++ b/rs_bindings_from_cc/generate_bindings/generate_struct_and_union.rs @@ -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 diff --git a/rs_bindings_from_cc/test/golden/composable_bridging.h b/rs_bindings_from_cc/test/golden/composable_bridging.h index c0dafc35b..d1e5af4a9 100644 --- a/rs_bindings_from_cc/test/golden/composable_bridging.h +++ b/rs_bindings_from_cc/test/golden/composable_bridging.h @@ -19,6 +19,10 @@ struct // clang-format on CppStruct {}; +struct StructWithBridgeField { + CppStruct bridge_field; +}; + CppStruct ReturnCppStruct(); void TakeCppStruct(CppStruct); diff --git a/rs_bindings_from_cc/test/golden/composable_bridging_rs_api.rs b/rs_bindings_from_cc/test/golden/composable_bridging_rs_api.rs index 06df803d2..1f1497a9e 100644 --- a/rs_bindings_from_cc/test/golden/composable_bridging_rs_api.rs +++ b/rs_bindings_from_cc/test/golden/composable_bridging_rs_api.rs @@ -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; 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::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN21StructWithBridgeFieldC1Ev(&raw mut tmp as *mut _); + tmp.assume_init() + } + } +} + #[inline(always)] pub fn ReturnCppStruct() -> crate::RustStruct { unsafe { @@ -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, ); @@ -617,6 +646,11 @@ mod detail { } const _: () = { + assert!(::core::mem::size_of::() == 1); + assert!(::core::mem::align_of::() == 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::() == 12); assert!(::core::mem::align_of::() == 4); static_assertions::assert_impl_all!(crate::Vec3: Copy,Clone); diff --git a/rs_bindings_from_cc/test/golden/composable_bridging_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/composable_bridging_rs_api_impl.cc index 8eec14538..9cd511a6c 100644 --- a/rs_bindings_from_cc/test/golden/composable_bridging_rs_api_impl.cc +++ b/rs_bindings_from_cc/test/golden/composable_bridging_rs_api_impl.cc @@ -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,