Support type bridging by value in rs_bindings_from cc.

PiperOrigin-RevId: 666007202
Change-Id: I6f0d0a51c630ff21bb176639f02ee8e1ae873710
diff --git a/rs_bindings_from_cc/generate_bindings/generate_func.rs b/rs_bindings_from_cc/generate_bindings/generate_func.rs
index cdd2846..39ce478 100644
--- a/rs_bindings_from_cc/generate_bindings/generate_func.rs
+++ b/rs_bindings_from_cc/generate_bindings/generate_func.rs
@@ -1706,6 +1706,7 @@
     } else if !return_type.is_c_abi_compatible_by_value() {
         // For return types that can't be passed by value, create a new out parameter.
         // The lifetime doesn't matter, so we can insert a new anonymous lifetime here.
+        // TODO(yongheng): Switch to `void*`.
         out_param = Some(quote! {
             &mut ::core::mem::MaybeUninit< #return_type >
         });
@@ -1872,16 +1873,44 @@
     let mut param_idents =
         func.params.iter().map(|p| crate::format_cc_ident(&p.identifier.identifier)).collect_vec();
 
+    let mut conversion_externs = quote! {};
+    let mut conversion_stmts = quote! {};
+    let convert_ident = |ident: &TokenStream| -> TokenStream {
+        let ident = format_ident!("__converted_{}", ident.to_string());
+        quote! { #ident }
+    };
     let mut param_types = func
         .params
         .iter()
         .map(|p| {
-            let formatted = crate::format_cpp_type(&p.type_.cpp_type, &ir)?;
-            if !db.rs_type_kind(p.type_.rs_type.clone())?.is_c_abi_compatible_by_value() {
+            let cpp_type = crate::format_cpp_type(&p.type_.cpp_type, &ir)?;
+            let arg_type = db.rs_type_kind(p.type_.rs_type.clone())?;
+            if arg_type.is_bridge_type() {
+                match &arg_type {
+                    RsTypeKind::BridgeType { rust_to_cpp_converter, .. } => {
+                        let convert_function = crate::format_cc_ident(rust_to_cpp_converter);
+                        let ident = crate::format_cc_ident(&p.identifier.identifier);
+                        let cpp_ident = convert_ident(&ident);
+                        conversion_externs.extend(quote! {
+                            extern "C" void #convert_function(void* rust_struct, void* cpp_struct);
+                        });
+                        conversion_stmts.extend(quote! {
+                            ::crubit::LazyInit<#cpp_type> #cpp_ident;
+                        });
+                        conversion_stmts.extend(quote! {
+                            #convert_function(#ident, &#cpp_ident.val);
+                        });
+                        Ok(quote! { void* })
+                    }
+                    _ => {
+                        bail!("Invalid bridge type: {:?}", arg_type);
+                    }
+                }
+            } else if !arg_type.is_c_abi_compatible_by_value() {
                 // non-Unpin types are wrapped by a pointer in the thunk.
-                Ok(quote! {#formatted *})
+                Ok(quote! {#cpp_type *})
             } else {
-                Ok(formatted)
+                Ok(cpp_type)
             }
         })
         .collect::<Result<Vec<_>>>()?;
@@ -1890,7 +1919,11 @@
         .params
         .iter()
         .map(|p| {
-            let ident = crate::format_cc_ident(&p.identifier.identifier);
+            let mut ident = crate::format_cc_ident(&p.identifier.identifier);
+            if db.rs_type_kind(p.type_.rs_type.clone())?.is_bridge_type() {
+                let formatted_ident = convert_ident(&ident);
+                ident = quote! { &(#formatted_ident.val) };
+            }
             match p.type_.cpp_type.name.as_deref() {
                 Some("&") => Ok(quote! { * #ident }),
                 Some("&&") => Ok(quote! { std::move(* #ident) }),
@@ -1910,8 +1943,8 @@
     // value across `extern "C"` ABI.  (We do this after the arg_expressions
     // computation, so that it's only in the parameter list, not the argument
     // list.)
-    let is_return_value_c_abi_compatible =
-        db.rs_type_kind(func.return_type.rs_type.clone())?.is_c_abi_compatible_by_value();
+    let return_type_kind = db.rs_type_kind(func.return_type.rs_type.clone())?;
+    let is_return_value_c_abi_compatible = return_type_kind.is_c_abi_compatible_by_value();
 
     let return_type_name = if !is_return_value_c_abi_compatible {
         param_idents.insert(0, crate::format_cc_ident("__return"));
@@ -1919,7 +1952,18 @@
         let mut cc_return_type = func.return_type.cpp_type.clone();
         cc_return_type.is_const = false;
         let return_type_name = crate::format_cpp_type(&cc_return_type, &ir)?;
-        param_types.insert(0, quote! {#return_type_name *});
+        match &return_type_kind {
+            RsTypeKind::BridgeType { cpp_to_rust_converter, .. } => {
+                let convert_function = crate::format_cc_ident(cpp_to_rust_converter);
+                conversion_externs.extend(quote! {
+                    extern "C" void #convert_function(void* cpp_struct, void* rust_struct);
+                });
+                param_types.insert(0, quote! {void *});
+            }
+            _ => {
+                param_types.insert(0, quote! {#return_type_name *});
+            }
+        };
         quote! {void}
     } else {
         crate::format_cpp_type(&func.return_type.cpp_type, &ir)?
@@ -1956,10 +2000,21 @@
 
     let return_expr = quote! {#implementation_function( #( #arg_expressions ),* )};
     let return_stmt = if !is_return_value_c_abi_compatible {
-        // Explicitly use placement `new` so that we get guaranteed copy elision in
-        // C++17.
         let out_param = &param_idents[0];
-        quote! {new(#out_param) auto(#return_expr)}
+        match &return_type_kind {
+            RsTypeKind::BridgeType { cpp_to_rust_converter, .. } => {
+                let convert_function = crate::format_cc_ident(cpp_to_rust_converter);
+                quote! {
+                    auto __original_cpp_struct = #return_expr;
+                    #convert_function(&__original_cpp_struct, #out_param)
+                }
+            }
+            _ => {
+                // Explicitly use placement `new` so that we get guaranteed copy elision in
+                // C++17.
+                quote! {new(#out_param) auto(#return_expr)}
+            }
+        }
     } else {
         match func.return_type.cpp_type.name.as_deref() {
             Some("void") => return_expr,
@@ -1984,7 +2039,10 @@
     };
 
     Ok(quote! {
+        #conversion_externs
+
         extern "C" #return_type_name #thunk_ident( #( #param_types #param_idents ),* ) {
+            #conversion_stmts
             #return_stmt;
         }
     })
diff --git a/rs_bindings_from_cc/generate_bindings/generate_record.rs b/rs_bindings_from_cc/generate_bindings/generate_record.rs
index b5ac025..845a802 100644
--- a/rs_bindings_from_cc/generate_bindings/generate_record.rs
+++ b/rs_bindings_from_cc/generate_bindings/generate_record.rs
@@ -246,6 +246,10 @@
 /// Generates Rust source code for a given `Record` and associated assertions as
 /// a tuple.
 pub fn generate_record(db: &Database, record: &Rc<Record>) -> Result<GeneratedItem> {
+    // If the record has a bridge type, we don't need to generate any bindings.
+    if record.bridge_type_info.is_some() {
+        return Ok(GeneratedItem::default());
+    }
     let ir = db.ir();
     let crate_root_path = crate::crate_root_path_tokens(&ir);
     let ident = make_rs_ident(record.rs_name.as_ref());
diff --git a/rs_bindings_from_cc/generate_bindings/lib.rs b/rs_bindings_from_cc/generate_bindings/lib.rs
index 45fbcfd..84da6b4 100644
--- a/rs_bindings_from_cc/generate_bindings/lib.rs
+++ b/rs_bindings_from_cc/generate_bindings/lib.rs
@@ -1086,7 +1086,12 @@
         if ty.type_args.len() != 1 {
             bail!("Missing pointee/referent type (need exactly 1 type argument): {:?}", ty);
         }
-        Ok(Rc::new(get_type_args()?.remove(0)))
+        // TODO(b/351976044): Support bridge types by pointer/reference.
+        let pointee = get_type_args()?.pop().unwrap();
+        if pointee.is_bridge_type() {
+            bail!("Bridging types are not supported as pointee/referent types.");
+        }
+        Ok(Rc::new(pointee))
     };
     let get_lifetime = || -> Result<Lifetime> {
         if ty.lifetime_args.len() != 1 {
@@ -1145,7 +1150,13 @@
                         rs_imported_crate_name(&incomplete_record.owning_target, &ir),
                     )),
                 },
-                Item::Record(record) => RsTypeKind::new_record(record.clone(), &ir)?,
+                Item::Record(record) => {
+                    if record.bridge_type_info.is_some() {
+                        RsTypeKind::new_bridge_type(record.clone())?
+                    } else {
+                        RsTypeKind::new_record(record.clone(), &ir)?
+                    }
+                }
                 Item::Enum(enum_) => RsTypeKind::new_enum(enum_.clone(), &ir)?,
                 Item::TypeAlias(type_alias) => new_type_alias(db, type_alias.clone())?,
                 Item::TypeMapOverride(type_map_override) => {
@@ -1421,6 +1432,15 @@
             "internal/sizeof.h".into(),
         ));
     };
+
+    for record in ir.records() {
+        if record.bridge_type_info.is_some() {
+            internal_includes.insert(CcInclude::SupportLibHeader(
+                crubit_support_path_format.into(),
+                "internal/lazy_init.h".into(),
+            ));
+        }
+    }
     for crubit_header in ["internal/cxx20_backports.h", "internal/offsetof.h"] {
         internal_includes.insert(CcInclude::SupportLibHeader(
             crubit_support_path_format.into(),
diff --git a/rs_bindings_from_cc/generate_bindings/rs_snippet.rs b/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
index 8445004..d961486 100644
--- a/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
+++ b/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
@@ -341,6 +341,12 @@
     Slice(Rc<RsTypeKind>),
     /// Nullable T, using the rust Option type.
     Option(Rc<RsTypeKind>),
+    BridgeType {
+        name: Rc<str>,
+        cpp_to_rust_converter: Rc<str>,
+        rust_to_cpp_converter: Rc<str>,
+        original_type: Rc<Record>,
+    },
     Other {
         name: Rc<str>,
         type_args: Rc<[RsTypeKind]>,
@@ -367,6 +373,19 @@
         Ok(RsTypeKind::Enum { enum_, crate_path })
     }
 
+    pub fn new_bridge_type(original_record: Rc<Record>) -> Result<Self> {
+        if let Some(bridge_type_info) = &original_record.bridge_type_info {
+            Ok(RsTypeKind::BridgeType {
+                name: bridge_type_info.bridge_type.clone(),
+                cpp_to_rust_converter: bridge_type_info.cpp_to_rust_converter.clone(),
+                rust_to_cpp_converter: bridge_type_info.rust_to_cpp_converter.clone(),
+                original_type: original_record,
+            })
+        } else {
+            bail!("Record does not have bridge type info: {:?}", original_record);
+        }
+    }
+
     pub fn new_type_map_override(
         db: &dyn BindingsGenerator,
         type_map_override: &TypeMapOverride,
@@ -404,6 +423,7 @@
             RsTypeKind::IncompleteRecord { .. } => false,
             RsTypeKind::Record { record, .. } => record.is_unpin(),
             RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.is_unpin(),
+            RsTypeKind::BridgeType { .. } => true,
             _ => true,
         }
     }
@@ -417,6 +437,10 @@
         matches!(self, RsTypeKind::Pointer { .. })
     }
 
+    pub fn is_bridge_type(&self) -> bool {
+        matches!(self, RsTypeKind::BridgeType { .. })
+    }
+
     /// Returns the features required to use this type which are not already
     /// enabled.
     ///
@@ -504,6 +528,7 @@
                 RsTypeKind::Primitive { .. } => require_feature(CrubitFeature::Supported, None),
                 RsTypeKind::Slice { .. } => require_feature(CrubitFeature::Supported, None),
                 RsTypeKind::Option { .. } => require_feature(CrubitFeature::Supported, None),
+                RsTypeKind::BridgeType { .. } => require_feature(CrubitFeature::Experimental, None),
                 // Fallback case, we can't really give a good error message here.
                 RsTypeKind::Other { .. } => require_feature(CrubitFeature::Experimental, None),
             }
@@ -534,6 +559,7 @@
             // TODO(b/274177296): Return `true` for structs where bindings replicate the type of
             // all the fields.
             RsTypeKind::Record { .. } => false,
+            RsTypeKind::BridgeType { .. } => false,
             RsTypeKind::Other { is_same_abi, .. } => *is_same_abi,
             _ => true,
         }
@@ -553,6 +579,7 @@
             RsTypeKind::TypeAlias { underlying_type, .. } => {
                 underlying_type.is_move_constructible()
             }
+            RsTypeKind::BridgeType { .. } => true,
             _ => true,
         }
     }
@@ -656,6 +683,8 @@
             RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.implements_copy(),
             RsTypeKind::Option(t) => t.implements_copy(),
             RsTypeKind::Slice(t) => t.implements_copy(),
+            // We cannot get the information of the Rust type so we assume it is not Copy.
+            RsTypeKind::BridgeType { .. } => false,
             RsTypeKind::Other { type_args, .. } => {
                 // All types that may appear here without `type_args` (e.g.
                 // primitive types like `i32`) implement `Copy`. Generic types
@@ -795,6 +824,7 @@
                 // TODO(jeanpierreda): This should likely be `::core::option::Option`.
                 quote! {Option<#type_arg>}
             }
+            RsTypeKind::BridgeType { .. } => self.to_token_stream(),
             RsTypeKind::Other { name, type_args, .. } => {
                 let name: TokenStream = name.parse().expect("Invalid RsType::name in the IR");
                 let generic_params =
@@ -888,6 +918,10 @@
                 // TODO(jeanpierreda): This should likely be `::core::option::Option`.
                 quote! {Option<#t>}
             }
+            RsTypeKind::BridgeType { name, .. } => {
+                let ident = make_rs_ident(name);
+                quote! { #ident }
+            }
             RsTypeKind::Other { name, type_args, .. } => {
                 let name: TokenStream = name.parse().expect("Invalid RsType::name in the IR");
                 let generic_params =
@@ -930,6 +964,7 @@
                     }
                     RsTypeKind::Slice(t) => self.todo.push(t),
                     RsTypeKind::Option(t) => self.todo.push(t),
+                    RsTypeKind::BridgeType { .. } => {}
                     RsTypeKind::Other { type_args, .. } => self.todo.extend(type_args.iter().rev()),
                 };
                 Some(curr)
diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc
index a6a6c01..3f6d0c4 100644
--- a/rs_bindings_from_cc/importers/cxx_record.cc
+++ b/rs_bindings_from_cc/importers/cxx_record.cc
@@ -122,36 +122,36 @@
   llvm::report_fatal_error("Unrecognized clang::TagKind");
 }
 
-std::optional<BridgingTypeInfo> GetBridgingTypeInfo(
+std::optional<BridgeTypeInfo> GetBridgeTypeInfo(
     const clang::RecordDecl* record_decl) {
-  constexpr absl::string_view kBridgingTypeTag = "crubit_bridging_type";
-  constexpr absl::string_view kBridgingTypeRustToCppConverterTag =
-      "crubit_bridging_type_rust_to_cpp_converter";
-  constexpr absl::string_view kBridgingTypeCppToRustConverterTag =
-      "crubit_bridging_type_cpp_to_rust_converter";
-  CHECK_OK(RequireSingleStringArgIfExists(record_decl, kBridgingTypeTag));
+  constexpr absl::string_view kBridgeTypeTag = "crubit_bridge_type";
+  constexpr absl::string_view kBridgeTypeRustToCppConverterTag =
+      "crubit_bridge_type_rust_to_cpp_converter";
+  constexpr absl::string_view kBridgeTypeCppToRustConverterTag =
+      "crubit_bridge_type_cpp_to_rust_converter";
+  CHECK_OK(RequireSingleStringArgIfExists(record_decl, kBridgeTypeTag));
   CHECK_OK(RequireSingleStringArgIfExists(record_decl,
-                                          kBridgingTypeRustToCppConverterTag));
+                                          kBridgeTypeRustToCppConverterTag));
   CHECK_OK(RequireSingleStringArgIfExists(record_decl,
-                                          kBridgingTypeCppToRustConverterTag));
-  auto bridging_type =
-      GetAnnotateArgAsStringByAttribute(record_decl, kBridgingTypeTag);
-  auto bridging_type_rust_to_cpp_converter = GetAnnotateArgAsStringByAttribute(
-      record_decl, kBridgingTypeRustToCppConverterTag);
-  auto bridging_type_cpp_to_rust_converter = GetAnnotateArgAsStringByAttribute(
-      record_decl, kBridgingTypeCppToRustConverterTag);
+                                          kBridgeTypeCppToRustConverterTag));
+  auto bridge_type =
+      GetAnnotateArgAsStringByAttribute(record_decl, kBridgeTypeTag);
+  auto bridge_type_rust_to_cpp_converter = GetAnnotateArgAsStringByAttribute(
+      record_decl, kBridgeTypeRustToCppConverterTag);
+  auto bridge_type_cpp_to_rust_converter = GetAnnotateArgAsStringByAttribute(
+      record_decl, kBridgeTypeCppToRustConverterTag);
 
-  if (bridging_type.has_value()) {
-    CHECK(bridging_type_rust_to_cpp_converter.has_value())
-        << "Missing " << kBridgingTypeRustToCppConverterTag << " for "
-        << kBridgingTypeTag << " " << *bridging_type;
-    CHECK(bridging_type_cpp_to_rust_converter.has_value())
-        << "Missing " << kBridgingTypeCppToRustConverterTag << " for "
-        << kBridgingTypeTag << " " << *bridging_type;
-    return BridgingTypeInfo{
-        .bridging_type = *bridging_type,
-        .rust_to_cpp_converter = *bridging_type_rust_to_cpp_converter,
-        .cpp_to_rust_converter = *bridging_type_cpp_to_rust_converter};
+  if (bridge_type.has_value()) {
+    CHECK(bridge_type_rust_to_cpp_converter.has_value())
+        << "Missing " << kBridgeTypeRustToCppConverterTag << " for "
+        << kBridgeTypeTag << " " << *bridge_type;
+    CHECK(bridge_type_cpp_to_rust_converter.has_value())
+        << "Missing " << kBridgeTypeCppToRustConverterTag << " for "
+        << kBridgeTypeTag << " " << *bridge_type;
+    return BridgeTypeInfo{
+        .bridge_type = *bridge_type,
+        .rust_to_cpp_converter = *bridge_type_rust_to_cpp_converter,
+        .cpp_to_rust_converter = *bridge_type_cpp_to_rust_converter};
   }
   return std::nullopt;
 }
@@ -374,7 +374,7 @@
       .defining_target = std::move(defining_target),
       .unknown_attr = std::move(unknown_attr),
       .doc_comment = std::move(doc_comment),
-      .bridging_type_info = GetBridgingTypeInfo(record_decl),
+      .bridge_type_info = GetBridgeTypeInfo(record_decl),
       .source_loc = ictx_.ConvertSourceLocation(source_loc),
       .unambiguous_public_bases = GetUnambiguousPublicBases(*record_decl),
       .fields = ImportFields(record_decl),
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index 499030c..82ef44c 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -442,9 +442,9 @@
   };
 }
 
-llvm::json::Value BridgingTypeInfo::ToJson() const {
+llvm::json::Value BridgeTypeInfo::ToJson() const {
   return llvm::json::Object{
-      {"bridging_type", bridging_type},
+      {"bridge_type", bridge_type},
       {"rust_to_cpp_converter", rust_to_cpp_converter},
       {"cpp_to_rust_converter", cpp_to_rust_converter},
   };
@@ -466,7 +466,7 @@
       {"defining_target", defining_target},
       {"unknown_attr", unknown_attr},
       {"doc_comment", doc_comment},
-      {"bridging_type_info", bridging_type_info},
+      {"bridge_type_info", bridge_type_info},
       {"source_loc", source_loc},
       {"unambiguous_public_bases", unambiguous_public_bases},
       {"fields", fields},
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index 407fe08..2bf525c 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -551,10 +551,10 @@
   int64_t alignment;
 };
 
-struct BridgingTypeInfo {
+struct BridgeTypeInfo {
   llvm::json::Value ToJson() const;
 
-  std::string bridging_type;
+  std::string bridge_type;
   std::string rust_to_cpp_converter;
   std::string cpp_to_rust_converter;
 };
@@ -575,7 +575,7 @@
   std::optional<BazelLabel> defining_target;
   std::optional<std::string> unknown_attr;
   std::optional<std::string> doc_comment;
-  std::optional<BridgingTypeInfo> bridging_type_info;
+  std::optional<BridgeTypeInfo> bridge_type_info;
   std::string source_loc;
   std::vector<BaseClass> unambiguous_public_bases;
   std::vector<Field> fields;
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index f537840..63e48a3 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -630,8 +630,8 @@
 }
 #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
 #[serde(deny_unknown_fields)]
-pub struct BridgingTypeInfo {
-    pub bridging_type: Rc<str>,
+pub struct BridgeTypeInfo {
+    pub bridge_type: Rc<str>,
     pub rust_to_cpp_converter: Rc<str>,
     pub cpp_to_rust_converter: Rc<str>,
 }
@@ -654,7 +654,7 @@
     /// default-closed and do not expose functions with unknown attributes.
     pub unknown_attr: Option<Rc<str>>,
     pub doc_comment: Option<Rc<str>>,
-    pub bridging_type_info: Option<BridgingTypeInfo>,
+    pub bridge_type_info: Option<BridgeTypeInfo>,
     pub source_loc: Rc<str>,
     pub unambiguous_public_bases: Vec<BaseClass>,
     pub fields: Vec<Field>,
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index 71c34ab..525c2e1 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -554,13 +554,13 @@
 }
 
 #[gtest]
-fn test_struct_with_bridging_type_annotation() {
+fn test_struct_with_bridge_type_annotation() {
     let ir = ir_from_cc(
         r#"
-        struct [[clang::annotate("crubit_bridging_type", "SomeBridgingType"),
-                 clang::annotate("crubit_bridging_type_rust_to_cpp_converter", "cpp_to_rust_converter"),
-                 clang::annotate("crubit_bridging_type_cpp_to_rust_converter", "rust_to_cpp_converter")]]
-                RecordWithBridgingType {
+        struct [[clang::annotate("crubit_bridge_type", "SomeBridgeType"),
+                 clang::annotate("crubit_bridge_type_rust_to_cpp_converter", "cpp_to_rust_converter"),
+                 clang::annotate("crubit_bridge_type_cpp_to_rust_converter", "rust_to_cpp_converter")]]
+                RecordWithBridgeType {
             int foo;
         };"#,
     )
@@ -570,9 +570,9 @@
         ir,
         quote! {
             Record {
-                rs_name: "RecordWithBridgingType", ...
-                bridging_type_info: Some(BridgingTypeInfo {
-                  bridging_type: "SomeBridgingType",
+                rs_name: "RecordWithBridgeType", ...
+                bridge_type_info: Some(BridgeTypeInfo {
+                  bridge_type: "SomeBridgeType",
                   rust_to_cpp_converter: "cpp_to_rust_converter",
                   cpp_to_rust_converter: "rust_to_cpp_converter",
                 }), ...
@@ -2465,7 +2465,7 @@
               defining_target: None,
               unknown_attr: None,
               doc_comment: Some(...),
-              bridging_type_info: None,
+              bridge_type_info: None,
               source_loc: "Generated from: google3/ir_from_cc_virtual_header.h;l=15",
               unambiguous_public_bases: [],
               fields: [Field {
diff --git a/rs_bindings_from_cc/test/bridging/BUILD b/rs_bindings_from_cc/test/bridging/BUILD
new file mode 100644
index 0000000..b4c17d0
--- /dev/null
+++ b/rs_bindings_from_cc/test/bridging/BUILD
@@ -0,0 +1,36 @@
+load(
+    "//common:crubit_wrapper_macros_oss.bzl",
+    "crubit_rust_test",
+)
+load(
+    "//rs_bindings_from_cc/bazel_support:additional_rust_srcs_for_crubit_bindings_aspect_hint.bzl",
+    "additional_rust_srcs_for_crubit_bindings",
+)
+
+cc_library(
+    name = "bridging_lib",
+    hdrs = ["bridging_lib.h"],
+    aspect_hints = [
+        "//features:experimental",
+        ":converter",
+    ],
+    deps = [
+        "//support/internal:bindings_support",
+    ],
+)
+
+additional_rust_srcs_for_crubit_bindings(
+    name = "converter",
+    srcs = ["converter.rs"],
+)
+
+crubit_rust_test(
+    name = "test",
+    srcs = ["test.rs"],
+    cc_deps = [
+        ":bridging_lib",
+    ],
+    deps = [
+        "@crate_index//:googletest",
+    ],
+)
diff --git a/rs_bindings_from_cc/test/bridging/bridging_lib.h b/rs_bindings_from_cc/test/bridging/bridging_lib.h
new file mode 100644
index 0000000..820c7bb
--- /dev/null
+++ b/rs_bindings_from_cc/test/bridging/bridging_lib.h
@@ -0,0 +1,45 @@
+// 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_RS_BINDINGS_FROM_CC_TEST_BRIDGE_BIRDGE_LIB_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_BRIDGE_BIRDGE_LIB_H_
+#include <cstddef>
+#include <string>
+#include <utility>
+
+#include "support/internal/attribute_macros.h"
+
+struct CRUBIT_INTERNAL_BRIDGE_SUPPORT("MyRustStruct", "rust_to_cpp_converter",
+                                      "cpp_to_rust_converter") CppStruct {
+  std::string s;
+
+  explicit CppStruct(std::string s) : s(std::move(s)) {}
+};
+
+inline size_t CalStructSize(CppStruct x) { return x.s.size(); }
+inline CppStruct ReturnHelloWorldStruct() { return CppStruct{"hello world"}; }
+
+inline CppStruct PadADot(CppStruct x) {
+  x.s += ".";
+  return x;
+}
+
+inline CppStruct Concat(CppStruct x, CppStruct y) {
+  x.s += y.s;
+  return x;
+}
+
+inline void ffi_create_my_cpp_struct(const char* s, size_t len, void* output) {
+  *(reinterpret_cast<CppStruct*>(output)) = CppStruct(std::string(s, len));
+}
+
+inline const char* ffi_get_buffer(const void* input) {
+  return reinterpret_cast<const CppStruct*>(input)->s.c_str();
+}
+
+inline size_t ffi_get_buffer_len(const void* input) {
+  return reinterpret_cast<const CppStruct*>(input)->s.size();
+}
+
+#endif  // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_BRIDGE_BIRDGE_LIB_H_
diff --git a/rs_bindings_from_cc/test/bridging/converter.rs b/rs_bindings_from_cc/test/bridging/converter.rs
new file mode 100644
index 0000000..7e23c0f
--- /dev/null
+++ b/rs_bindings_from_cc/test/bridging/converter.rs
@@ -0,0 +1,39 @@
+// 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
+
+extern crate std;
+use core::mem::MaybeUninit;
+use std::os::raw::c_void;
+use std::slice;
+use std::string::String;
+
+pub struct MyRustStruct {
+    pub s: String,
+}
+
+impl MyRustStruct {
+    pub fn new(s: &str) -> Self {
+        MyRustStruct { s: String::from(s) }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rust_to_cpp_converter(x: *const c_void, output: *mut c_void) {
+    unsafe {
+        let x = &*(x as *const MyRustStruct);
+        crate::ffi_create_my_cpp_struct(x.s.as_ptr() as _, x.s.len(), output);
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn cpp_to_rust_converter(input: *const c_void, output: *mut c_void) {
+    unsafe {
+        let output = &mut *(output as *mut MaybeUninit<MyRustStruct>);
+        let buffer = crate::ffi_get_buffer(input);
+        let len = crate::ffi_get_buffer_len(input);
+        output.as_mut_ptr().write(MyRustStruct {
+            s: String::from_utf8_unchecked(slice::from_raw_parts(buffer as _, len).into()),
+        });
+    }
+}
diff --git a/rs_bindings_from_cc/test/bridging/test.rs b/rs_bindings_from_cc/test/bridging/test.rs
new file mode 100644
index 0000000..32208b1
--- /dev/null
+++ b/rs_bindings_from_cc/test/bridging/test.rs
@@ -0,0 +1,30 @@
+// 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
+
+use googletest::prelude::*;
+
+#[gtest]
+fn test_bridge_type_as_function_arg() {
+    let x = bridging_lib::MyRustStruct::new("hello");
+    assert_eq!(bridging_lib::CalStructSize(x), 5);
+}
+
+#[gtest]
+fn test_bridge_type_as_return_value() {
+    assert_eq!(bridging_lib::ReturnHelloWorldStruct().s, String::from("hello world"));
+}
+
+#[gtest]
+fn test_bridge_type_two_args_are_bridged() {
+    let x = bridging_lib::MyRustStruct::new("hello");
+    let y = bridging_lib::MyRustStruct::new(" world");
+    assert_eq!(bridging_lib::Concat(x, y).s, String::from("hello world"));
+}
+
+#[gtest]
+fn test_round_trip() {
+    let x = bridging_lib::MyRustStruct::new("hello");
+    let y = bridging_lib::PadADot(x);
+    assert_eq!(y.s, "hello.");
+}
diff --git a/rs_bindings_from_cc/test/golden/BUILD b/rs_bindings_from_cc/test/golden/BUILD
index 0a2010e..7656692 100644
--- a/rs_bindings_from_cc/test/golden/BUILD
+++ b/rs_bindings_from_cc/test/golden/BUILD
@@ -73,11 +73,18 @@
     tags = [tag for tag in (TAGS[name] if name in TAGS else [])],
 ) for name in TESTS]
 
+NON_BUILDABLE_TEST = [
+    # The bridge type is not defined in the C++ header so it cannot be built.
+    "bridge_type",
+]
+
+BUILDABLE_TESTS = [name for name in TESTS if name not in NON_BUILDABLE_TEST]
+
 [crubit_rust_test(
     name = name + "_rs_test",
     srcs = ["empty_rs_test.rs"],
     cc_deps = ["%s_cc" % name],
-) for name in TESTS]
+) for name in BUILDABLE_TESTS]
 
 cc_library(
     name = "namespaces_json",
diff --git a/rs_bindings_from_cc/test/golden/bridge_type.h b/rs_bindings_from_cc/test/golden/bridge_type.h
new file mode 100644
index 0000000..30f328f
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/bridge_type.h
@@ -0,0 +1,24 @@
+// 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_RS_BINDINGS_FROM_CC_TEST_GOLDEN_BRIDGE_TYPE_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_BRIDGE_TYPE_H_
+
+struct [[clang::annotate("crubit_bridge_type", "RustStruct"),
+         clang::annotate("crubit_bridge_type_rust_to_cpp_converter",
+                         "rust_to_cpp_converter"),
+         clang::annotate("crubit_bridge_type_cpp_to_rust_converter",
+                         "cpp_to_rust_converter")]] CppStruct {
+  ~CppStruct();
+};
+
+CppStruct ReturnCppStruct();
+
+void TakeCppStruct(CppStruct);
+
+void TakeCppStructByPtr(CppStruct*);
+
+CppStruct* ReturnCppStructByPtr();
+
+#endif  // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_BRIDGE_TYPE_H_
diff --git a/rs_bindings_from_cc/test/golden/bridge_type_rs_api.rs b/rs_bindings_from_cc/test/golden/bridge_type_rs_api.rs
new file mode 100644
index 0000000..cf145c6
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/bridge_type_rs_api.rs
@@ -0,0 +1,47 @@
+// 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:
+// //rs_bindings_from_cc/test/golden:bridge_type_cc
+// Features: experimental, supported
+
+#![rustfmt::skip]
+#![feature(custom_inner_attributes)]
+#![allow(stable_features)]
+#![no_std]
+#![allow(improper_ctypes)]
+#![allow(nonstandard_style)]
+#![allow(dead_code)]
+#![deny(warnings)]
+
+#[inline(always)]
+pub fn ReturnCppStruct() -> RustStruct {
+    unsafe {
+        let mut __return = ::core::mem::MaybeUninit::<RustStruct>::uninit();
+        crate::detail::__rust_thunk___Z15ReturnCppStructv(&mut __return);
+        __return.assume_init()
+    }
+}
+
+#[inline(always)]
+pub fn TakeCppStruct(mut __param_0: RustStruct) {
+    unsafe { crate::detail::__rust_thunk___Z13TakeCppStruct9CppStruct(&mut __param_0) }
+}
+
+// Error while generating bindings for item 'TakeCppStructByPtr':
+// Failed to format type of parameter 0: Bridging types are not supported as pointee/referent types.
+
+// Error while generating bindings for item 'ReturnCppStructByPtr':
+// Failed to format return type: Bridging types are not supported as pointee/referent types.
+
+mod detail {
+    #[allow(unused_imports)]
+    use super::*;
+    extern "C" {
+        pub(crate) fn __rust_thunk___Z15ReturnCppStructv(
+            __return: &mut ::core::mem::MaybeUninit<RustStruct>,
+        );
+        pub(crate) fn __rust_thunk___Z13TakeCppStruct9CppStruct(__param_0: &mut RustStruct);
+    }
+}
diff --git a/rs_bindings_from_cc/test/golden/bridge_type_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/bridge_type_rs_api_impl.cc
new file mode 100644
index 0000000..408451a
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/bridge_type_rs_api_impl.cc
@@ -0,0 +1,36 @@
+// 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:
+// //rs_bindings_from_cc/test/golden:bridge_type_cc
+// Features: experimental, supported
+
+#include "support/internal/cxx20_backports.h"
+#include "support/internal/lazy_init.h"
+#include "support/internal/offsetof.h"
+#include "support/internal/sizeof.h"
+
+#include <cstddef>
+#include <memory>
+
+// Public headers of the C++ library being wrapped.
+#include "rs_bindings_from_cc/test/golden/bridge_type.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wthread-safety-analysis"
+
+extern "C" void cpp_to_rust_converter(void* cpp_struct, void* rust_struct);
+extern "C" void __rust_thunk___Z15ReturnCppStructv(void* __return) {
+  auto __original_cpp_struct = ReturnCppStruct();
+  cpp_to_rust_converter(&__original_cpp_struct, __return);
+}
+
+extern "C" void rust_to_cpp_converter(void* rust_struct, void* cpp_struct);
+extern "C" void __rust_thunk___Z13TakeCppStruct9CppStruct(void* __param_0) {
+  ::crubit::LazyInit<struct CppStruct> __converted___param_0;
+  rust_to_cpp_converter(__param_0, &__converted___param_0.val);
+  TakeCppStruct(std::move(*&(__converted___param_0.val)));
+}
+
+#pragma clang diagnostic pop
diff --git a/support/internal/BUILD b/support/internal/BUILD
index 06cf9bd..cebffc0 100644
--- a/support/internal/BUILD
+++ b/support/internal/BUILD
@@ -7,6 +7,7 @@
     hdrs = [
         "attribute_macros.h",
         "cxx20_backports.h",
+        "lazy_init.h",
         "memswap.h",
         "offsetof.h",
         "return_value_slot.h",
diff --git a/support/internal/attribute_macros.h b/support/internal/attribute_macros.h
index 2f0bee6..20c36ef 100644
--- a/support/internal/attribute_macros.h
+++ b/support/internal/attribute_macros.h
@@ -71,25 +71,25 @@
 #define CRUBIT_INTERNAL_SAME_ABI \
   CRUBIT_INTERNAL_ANNOTATE("crubit_internal_same_abi")
 
-#define CRUBIT_INTERNAL_BRIDGING_TYPE(t) \
-  CRUBIT_INTERNAL_ANNOTATE("crubit_bridging_type", t)
+#define CRUBIT_INTERNAL_BRIDGE_TYPE(t) \
+  CRUBIT_INTERNAL_ANNOTATE("crubit_bridge_type", t)
 
-#define CRUBIT_INTERNAL_BRIDGING_TYPE_RUST_TO_CPP_CONVERTER(t) \
-  CRUBIT_INTERNAL_ANNOTATE("crubit_bridging_type_rust_to_cpp_converter", t)
+#define CRUBIT_INTERNAL_BRIDGE_TYPE_RUST_TO_CPP_CONVERTER(t) \
+  CRUBIT_INTERNAL_ANNOTATE("crubit_bridge_type_rust_to_cpp_converter", t)
 
-#define CRUBIT_INTERNAL_BRIDGING_TYPE_CPP_TO_RUST_CONVERTER(t) \
-  CRUBIT_INTERNAL_ANNOTATE("crubit_bridging_type_cpp_to_rust_converter", t)
+#define CRUBIT_INTERNAL_BRIDGE_TYPE_CPP_TO_RUST_CONVERTER(t) \
+  CRUBIT_INTERNAL_ANNOTATE("crubit_bridge_type_cpp_to_rust_converter", t)
 
-// Declare a Rust type as the bridging type for binding generation.
+// Declare a Rust type as the bridge type for binding generation.
 //
 // This can be applied to a struct, class, or enum.
 //
-// For example, We have the following C++ struct and its Rust bridging
+// For example, We have the following C++ struct and its Rust bridge
 // counterpart:
 //
 // ```c++
 // struct
-//   CRUBIT_INTERNAL_BRIDGING_SUPPORT("MyRustStruct", "rust_to_cpp",
+//   CRUBIT_INTERNAL_BRIDGE_SUPPORT("MyRustStruct", "rust_to_cpp",
 //   "cpp_to_rust") MyCppStruct {
 //     std::string name;
 // };
@@ -111,11 +111,14 @@
 // ```
 //
 // SAFETY:
-//   `ty` must be a fully-qualified valid Rust type name.
-//   `rust_to_cpp` and `cpp_to_rust` must be valid function names.
-#define CRUBIT_INTERNAL_BRIDGING_SUPPORT(ty, rust_to_cpp, cpp_to_rust) \
-  CRUBIT_INTERNAL_BRIDGING_TYPE(ty)                                    \
-  CRUBIT_INTERNAL_BRIDGING_TYPE_RUST_TO_CPP_CONVERTER(rust_to_cpp)     \
-  CRUBIT_INTERNAL_BRIDGING_TYPE_CPP_TO_RUST_CONVERTER(cpp_to_rust)
+//   * `ty` must be a fully-qualified valid Rust type name.
+//   * `rust_to_cpp` must be a valid function name, and its signature must be
+//     `void rust_to_cpp (void* rust_struct, MyCppStruct* cpp_struct)`.
+//   * `cpp_to_rust` must be valid function name and its signature must be
+//     `void cpp_to_rust (MyCppStruct* cpp_struct, void* rust_struct)`.
+#define CRUBIT_INTERNAL_BRIDGE_SUPPORT(ty, rust_to_cpp, cpp_to_rust) \
+  CRUBIT_INTERNAL_BRIDGE_TYPE(ty)                                    \
+  CRUBIT_INTERNAL_BRIDGE_TYPE_RUST_TO_CPP_CONVERTER(rust_to_cpp)     \
+  CRUBIT_INTERNAL_BRIDGE_TYPE_CPP_TO_RUST_CONVERTER(cpp_to_rust)
 
 #endif  // CRUBIT_SUPPORT_INTERNAL_ATTRIBUTES_H_
diff --git a/support/internal/lazy_init.h b/support/internal/lazy_init.h
new file mode 100644
index 0000000..fbd9265
--- /dev/null
+++ b/support/internal/lazy_init.h
@@ -0,0 +1,20 @@
+// 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_SUPPORT_INTERNAL_LAZY_INIT_H_
+#define THIRD_PARTY_CRUBIT_SUPPORT_INTERNAL_LAZY_INIT_H_
+#include <memory>
+
+namespace crubit {
+
+template <typename T>
+union LazyInit {
+  constexpr LazyInit() {}
+  ~LazyInit() { std::destroy_at(&this->val); }
+  T val;
+};
+
+}  // namespace crubit
+
+#endif  // THIRD_PARTY_CRUBIT_SUPPORT_INTERNAL_LAZY_INIT_H_