rs_bindings_from_cc: Use an attribute to dynamically specify type mapping.

---

This Cl allows you to override Crubit's type-mapping, so that instead of generating bindings for a type `X`, it instead looks up an already-existing type `Y`. This is useful, for example:

* For specialty types like `rs_char`, where the other language's equivalent is a builtin type.
* For types with native language support, like protocol buffers, where we want to use the native languages' proto rather than generated bindings
* For types which are themselves bindings: the bindings for the bindings for `X` should be `X` again, not an infinite-turtle bindings-of-bindings situation.

This is the first step to all of those: for calling C++ from Rust, we allow C++ types to override which Rust type they are.

A rough TODO list of next steps:

1. suppress bindings generation for the record itself, when annotated with `crubit_rust_type`.
2. apply `crubit_rust_type` annotations to generated bindings, so that bindings-of-bindings can DTRT.
3. do ~something about crubit features. Currently they would block round-tripped bindings.
4. rinse and repeat for cc_bindings_from_rs.

PiperOrigin-RevId: 529212737
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index 3aafd29..61f456e 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -160,13 +160,18 @@
 )
 
 cc_library(
-    name = "known_types_map",
-    srcs = ["known_types_map.cc"],
-    hdrs = ["known_types_map.h"],
+    name = "type_map",
+    srcs = ["type_map.cc"],
+    hdrs = ["type_map.h"],
     visibility = ["//:__subpackages__"],
     deps = [
+        ":cc_ir",
+        "//common:status_macros",
         "@absl//absl/container:flat_hash_map",
+        "@absl//absl/status",
+        "@absl//absl/status:statusor",
         "@absl//absl/strings",
+        "@llvm-project//clang:ast",
     ],
 )
 
@@ -250,7 +255,7 @@
         ":bazel_types",
         ":cc_ir",
         ":decl_importer",
-        ":known_types_map",
+        ":type_map",
         "//common:status_macros",
         "//lifetime_annotations:type_lifetimes",
         "//rs_bindings_from_cc/importers:class_template",
@@ -418,7 +423,6 @@
     hdrs = ["src_code_gen.h"],
     deps = [
         ":cc_ir",
-        ":cmdline",
         ":src_code_gen_impl",  # buildcleaner: keep
         "//common:cc_ffi_types",
         "//common:status_macros",
diff --git a/rs_bindings_from_cc/importer.cc b/rs_bindings_from_cc/importer.cc
index 3afb187..94fb472 100644
--- a/rs_bindings_from_cc/importer.cc
+++ b/rs_bindings_from_cc/importer.cc
@@ -33,7 +33,7 @@
 #include "rs_bindings_from_cc/ast_util.h"
 #include "rs_bindings_from_cc/bazel_types.h"
 #include "rs_bindings_from_cc/ir.h"
-#include "rs_bindings_from_cc/known_types_map.h"
+#include "rs_bindings_from_cc/type_map.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
@@ -759,9 +759,9 @@
   // Qualifiers are handled separately in ConvertQualType().
   std::string type_string = clang::QualType(type, 0).getAsString();
 
-  if (auto maybe_mapped_type = MapKnownCcTypeToRsType(type_string);
-      maybe_mapped_type.has_value()) {
-    return MappedType::Simple(std::string(*maybe_mapped_type), type_string);
+  CRUBIT_ASSIGN_OR_RETURN(auto override_type, TypeMapOverride(*type));
+  if (override_type.has_value()) {
+    return *std::move(override_type);
   } else if (type->isPointerType() || type->isLValueReferenceType() ||
              type->isRValueReferenceType()) {
     clang::QualType pointee_type = type->getPointeeType();
diff --git a/rs_bindings_from_cc/importers/BUILD b/rs_bindings_from_cc/importers/BUILD
index 66da70c..35f1126 100644
--- a/rs_bindings_from_cc/importers/BUILD
+++ b/rs_bindings_from_cc/importers/BUILD
@@ -98,7 +98,7 @@
     deps = [
         "@absl//absl/log:check",
         "//rs_bindings_from_cc:decl_importer",
-        "//rs_bindings_from_cc:known_types_map",
+        "//rs_bindings_from_cc:type_map",
         "@llvm-project//clang:ast",
     ],
 )
diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc
index 181d1bc..be93427 100644
--- a/rs_bindings_from_cc/importers/cxx_record.cc
+++ b/rs_bindings_from_cc/importers/cxx_record.cc
@@ -181,14 +181,18 @@
     }
   }
 
-  // The less expensive `getName` comparison is done first, because the
-  // documentation of `NamedDecl::getQualifiedNameAsString` says that "it should
-  // be called only when performance doesn't matter".
+  // TODO(b/274834739): Automatically suppress this based on crubit_rust_type,
+  // similar to the logic for type aliases.
+  // TODO(b/274834739): emit size/align assertions for these mapped types.
+  //
+  // The less expensive `getName` comparison is done
+  // first, because the documentation of `NamedDecl::getQualifiedNameAsString`
+  // says that "it should be called only when performance doesn't matter".
   if (record_decl->getName() == "rs_char" &&
       record_decl->getQualifiedNameAsString() == "rs_std::rs_char") {
     return ictx_.ImportUnsupportedItem(
         record_decl,
-        "Round-tripping of `rs_char` is not supported yet (b/270160530)");
+        "Round-tripping of `rs_char` is not supported yet (b/274834739)");
   }
 
   absl::StatusOr<RecordType> record_type = TranslateRecordType(*record_decl);
diff --git a/rs_bindings_from_cc/importers/typedef_name.cc b/rs_bindings_from_cc/importers/typedef_name.cc
index 8a08e18..9e4eea8 100644
--- a/rs_bindings_from_cc/importers/typedef_name.cc
+++ b/rs_bindings_from_cc/importers/typedef_name.cc
@@ -7,7 +7,7 @@
 #include <optional>
 
 #include "absl/log/check.h"
-#include "rs_bindings_from_cc/known_types_map.h"
+#include "rs_bindings_from_cc/type_map.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
 
@@ -37,7 +37,8 @@
     // into their item, instead of having a separate TypeAlias item in addition.
     return std::nullopt;
   }
-  if (MapKnownCcTypeToRsType(type.getAsString()).has_value()) {
+  auto override_type = TypeMapOverride(*type.getTypePtr());
+  if (override_type.ok() && override_type->has_value()) {
     return std::nullopt;
   }
 
diff --git a/rs_bindings_from_cc/known_types_map.cc b/rs_bindings_from_cc/known_types_map.cc
deleted file mode 100644
index 40f35ef..0000000
--- a/rs_bindings_from_cc/known_types_map.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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
-
-#include "rs_bindings_from_cc/known_types_map.h"
-
-#include "absl/container/flat_hash_map.h"
-
-namespace crubit {
-
-// A mapping of C++ standard types to their equivalent Rust types.
-// To produce more idiomatic results, these types receive special handling
-// instead of using the generic type mapping mechanism.
-std::optional<absl::string_view> MapKnownCcTypeToRsType(
-    absl::string_view cc_type) {
-  static const auto* const kWellKnownTypes =
-      new absl::flat_hash_map<absl::string_view, absl::string_view>({
-          // TODO(lukasza): Try to deduplicate the entries below - for example:
-          // - Try to unify `std::int32_t` and `int32_t`
-          // - Try to unify `class rs_std::rs_char` and `rs_std::rs_char`
-          // One approach would be to desugar the types before calling
-          // `MapKnownCcTypeToRsType`, but note that desugaring of type aliases
-          // may be undesirable (i.e.  we may want the bindings to refer to
-          // `TypeAlias` rather than directly to the type that it desugars to).
-          // Note that b/254096006 tracks desire to preserve type aliases in
-          // `cc_bindings_from_rs`.
-          {"ptrdiff_t", "isize"},
-          {"intptr_t", "isize"},
-          {"size_t", "usize"},
-          {"uintptr_t", "usize"},
-          {"std::ptrdiff_t", "isize"},
-          {"std::intptr_t", "isize"},
-          {"std::size_t", "usize"},
-          {"std::uintptr_t", "usize"},
-
-          {"int8_t", "i8"},
-          {"int16_t", "i16"},
-          {"int32_t", "i32"},
-          {"int64_t", "i64"},
-          {"std::int8_t", "i8"},
-          {"std::int16_t", "i16"},
-          {"std::int32_t", "i32"},
-          {"std::int64_t", "i64"},
-
-          {"uint8_t", "u8"},
-          {"uint16_t", "u16"},
-          {"uint32_t", "u32"},
-
-          {"uint64_t", "u64"},
-          {"std::uint8_t", "u8"},
-          {"std::uint16_t", "u16"},
-          {"std::uint32_t", "u32"},
-          {"std::uint64_t", "u64"},
-
-          {"char16_t", "u16"},
-          {"char32_t", "u32"},
-          {"wchar_t", "i32"},
-
-          // `class rs_std::rs_char` key covers direct usage of
-          // `rs_std::rs_char`.  `rs_std::rs_char` key covers scenarios when
-          // `using` has imported `rs_char` into another namespace.  See also
-          // the deduplication TODO comment above.
-          {"class rs_std::rs_char", "char"},
-          {"rs_std::rs_char", "char"},
-      });
-  auto it = kWellKnownTypes->find(cc_type);
-  if (it == kWellKnownTypes->end()) return std::nullopt;
-  return it->second;
-}
-
-}  // namespace crubit
diff --git a/rs_bindings_from_cc/known_types_map.h b/rs_bindings_from_cc/known_types_map.h
deleted file mode 100644
index 925d676..0000000
--- a/rs_bindings_from_cc/known_types_map.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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_KNOWN_TYPES_MAP_H_
-#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_KNOWN_TYPES_MAP_H_
-
-#include <optional>
-
-#include "absl/strings/string_view.h"
-
-namespace crubit {
-
-// Converts primitive types like `std::usize` or `int64_t` into their Rust
-// equivalents.
-std::optional<absl::string_view> MapKnownCcTypeToRsType(
-    absl::string_view cc_type);
-
-}  // namespace crubit
-
-#endif  // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_KNOWN_TYPES_MAP_H_
diff --git a/rs_bindings_from_cc/test/types/BUILD b/rs_bindings_from_cc/test/types/BUILD
index 08021c4..d16babe 100644
--- a/rs_bindings_from_cc/test/types/BUILD
+++ b/rs_bindings_from_cc/test/types/BUILD
@@ -6,6 +6,7 @@
 crubit_test_cc_library(
     name = "types_nonptr",
     hdrs = ["types_nonptr.h"],
+    deps = ["//support/internal:bindings_support"],
 )
 
 crubit_test_cc_library(
diff --git a/rs_bindings_from_cc/test/types/types_nonptr.h b/rs_bindings_from_cc/test/types/types_nonptr.h
index 12561cd..78d5e32 100644
--- a/rs_bindings_from_cc/test/types/types_nonptr.h
+++ b/rs_bindings_from_cc/test/types/types_nonptr.h
@@ -11,6 +11,8 @@
 #include <cstddef>
 #include <cstdint>
 
+#include "support/internal/attribute_macros.h"
+
 // Not a template, so that it isn't visible to the bindings generator.
 // We're just here to save typing.
 #define TEST(Name, T)                            \
@@ -77,4 +79,18 @@
 struct ExampleStruct final {};
 TEST(Struct, ExampleStruct);
 
+struct CRUBIT_INTERNAL_RUST_TYPE("i8") MyI8Struct final {
+  signed char x;
+};
+
+struct CRUBIT_INTERNAL_RUST_TYPE("i8") MyI8Class final {
+  signed char x;
+};
+
+enum CRUBIT_INTERNAL_RUST_TYPE("i8") MyI8Enum : unsigned char { kX };
+
+TEST(TypeMapOverrideStruct, MyI8Struct);
+TEST(TypeMapOverrideClass, MyI8Class);
+TEST(TypeMapOverrideEnum, MyI8Enum);
+
 #endif  // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_TYPES_TYPES_NONPTR_H_
diff --git a/rs_bindings_from_cc/test/types/types_test.rs b/rs_bindings_from_cc/test/types/types_test.rs
index b8de88b..ea29907 100644
--- a/rs_bindings_from_cc/test/types/types_test.rs
+++ b/rs_bindings_from_cc/test/types/types_test.rs
@@ -113,6 +113,9 @@
     Double => f64,
 
     Struct => types_nonptr::ExampleStruct,
+    TypeMapOverrideStruct => i8,
+    TypeMapOverrideClass => i8,
+    TypeMapOverrideEnum => i8,
 );
 
 // TODO(b/228569417): These should all generate bindings and be & (mut) 'static.
diff --git a/rs_bindings_from_cc/type_map.cc b/rs_bindings_from_cc/type_map.cc
new file mode 100644
index 0000000..5f452b0
--- /dev/null
+++ b/rs_bindings_from_cc/type_map.cc
@@ -0,0 +1,144 @@
+// 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
+
+#include "rs_bindings_from_cc/type_map.h"
+
+#include <optional>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "common/status_macros.h"
+#include "rs_bindings_from_cc/ir.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/Attrs.inc"
+#include "clang/AST/Decl.h"
+
+namespace crubit {
+
+namespace {
+// A mapping of C++ standard types to their equivalent Rust types.
+std::optional<absl::string_view> MapKnownCcTypeToRsType(
+    absl::string_view cc_type) {
+  static const auto* const kWellKnownTypes =
+      new absl::flat_hash_map<absl::string_view, absl::string_view>({
+          // TODO(lukasza): Try to deduplicate the entries below - for example:
+          // - Try to unify `std::int32_t` and `int32_t`
+          // One approach would be to desugar the types before calling
+          // `MapKnownCcTypeToRsType`, but note that desugaring of type aliases
+          // may be undesirable (i.e.  we may want the bindings to refer to
+          // `TypeAlias` rather than directly to the type that it desugars to).
+          // Note that b/254096006 tracks desire to preserve type aliases in
+          // `cc_bindings_from_rs`.
+          {"ptrdiff_t", "isize"},
+          {"intptr_t", "isize"},
+          {"size_t", "usize"},
+          {"uintptr_t", "usize"},
+          {"std::ptrdiff_t", "isize"},
+          {"std::intptr_t", "isize"},
+          {"std::size_t", "usize"},
+          {"std::uintptr_t", "usize"},
+
+          {"int8_t", "i8"},
+          {"int16_t", "i16"},
+          {"int32_t", "i32"},
+          {"int64_t", "i64"},
+          {"std::int8_t", "i8"},
+          {"std::int16_t", "i16"},
+          {"std::int32_t", "i32"},
+          {"std::int64_t", "i64"},
+
+          {"uint8_t", "u8"},
+          {"uint16_t", "u16"},
+          {"uint32_t", "u32"},
+
+          {"uint64_t", "u64"},
+          {"std::uint8_t", "u8"},
+          {"std::uint16_t", "u16"},
+          {"std::uint32_t", "u32"},
+          {"std::uint64_t", "u64"},
+
+          {"char16_t", "u16"},
+          {"char32_t", "u32"},
+          {"wchar_t", "i32"},
+      });
+  auto it = kWellKnownTypes->find(cc_type);
+  if (it == kWellKnownTypes->end()) return std::nullopt;
+  return it->second;
+}
+
+// Copied from lifetime_annotations/type_lifetimes.cc, which is expected to move
+// into ClangTidy. See:
+// https://discourse.llvm.org/t/rfc-lifetime-annotations-for-c/61377
+absl::StatusOr<absl::string_view> EvaluateAsStringLiteral(
+    const clang::Expr& expr, const clang::ASTContext& ast_context) {
+  auto error = []() {
+    return absl::InvalidArgumentError(
+        "cannot evaluate argument as a string literal");
+  };
+
+  clang::Expr::EvalResult eval_result;
+  if (!expr.EvaluateAsConstantExpr(eval_result, ast_context) ||
+      !eval_result.Val.isLValue()) {
+    return error();
+  }
+
+  const auto* eval_result_expr =
+      eval_result.Val.getLValueBase().dyn_cast<const clang::Expr*>();
+  if (!eval_result_expr) {
+    return error();
+  }
+
+  const auto* string_literal =
+      clang::dyn_cast<clang::StringLiteral>(eval_result_expr);
+  if (!string_literal) {
+    return error();
+  }
+
+  return {string_literal->getString()};
+}
+
+absl::StatusOr<std::optional<absl::string_view>> GetRustTypeAttribute(
+    const clang::Type& cc_type) {
+  std::optional<absl::string_view> rust_type;
+  if (const clang::TagDecl* tag_decl = cc_type.getAsTagDecl();
+      tag_decl != nullptr) {
+    for (clang::AnnotateAttr* attr :
+         tag_decl->specific_attrs<clang::AnnotateAttr>()) {
+      if (attr->getAnnotation() != "crubit_internal_rust_type") continue;
+
+      if (rust_type.has_value())
+        return absl::InvalidArgumentError(
+            "Only one `crubit_internal_rust_type` attribute may be placed on a "
+            "type.");
+      if (attr->args_size() != 1)
+        return absl::InvalidArgumentError(
+            "The `crubit_internal_rust_type` attribute requires a single "
+            "string literal "
+            "argument, the Rust type.");
+      const clang::Expr& arg = **attr->args_begin();
+      CRUBIT_ASSIGN_OR_RETURN(
+          rust_type, EvaluateAsStringLiteral(arg, tag_decl->getASTContext()));
+    }
+  }
+  return rust_type;
+}
+
+}  // namespace
+
+absl::StatusOr<std::optional<MappedType>> TypeMapOverride(
+    const clang::Type& cc_type) {
+  std::string type_string = clang::QualType(&cc_type, 0).getAsString();
+  std::optional<absl::string_view> rust_type;
+  CRUBIT_ASSIGN_OR_RETURN(rust_type, GetRustTypeAttribute(cc_type));
+  if (!rust_type.has_value()) {
+    rust_type = MapKnownCcTypeToRsType(type_string);
+  }
+  if (rust_type.has_value()) {
+    return MappedType::Simple(std::string(*rust_type), type_string);
+  }
+  return std::nullopt;
+}
+
+}  // namespace crubit
diff --git a/rs_bindings_from_cc/type_map.h b/rs_bindings_from_cc/type_map.h
new file mode 100644
index 0000000..acb8f38
--- /dev/null
+++ b/rs_bindings_from_cc/type_map.h
@@ -0,0 +1,31 @@
+// 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_KNOWN_TYPES_MAP_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_KNOWN_TYPES_MAP_H_
+
+#include <optional>
+
+#include "absl/strings/string_view.h"
+#include "rs_bindings_from_cc/ir.h"
+#include "clang/AST/Type.h"
+
+namespace crubit {
+
+// Converts C++ types to an already-existing Rust type, instead of generating
+// bindings for the C++ type.
+//
+// The return value is a fully-qualified Rust name, including builtin type
+// names.
+//
+// For example, C++ `int64_t` becomes Rust `i64`.
+//
+// To create a new type mapping, either add the type to the hardcoded list
+// of types, or else add the `crubit_rust_type` attribute.
+absl::StatusOr<std::optional<MappedType>> TypeMapOverride(
+    const clang::Type& cc_type);
+
+}  // namespace crubit
+
+#endif  // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_KNOWN_TYPES_MAP_H_
diff --git a/support/internal/BUILD b/support/internal/BUILD
index 7750a9d..0e7fb77 100644
--- a/support/internal/BUILD
+++ b/support/internal/BUILD
@@ -3,6 +3,7 @@
 cc_library(
     name = "bindings_support",
     hdrs = [
+        "attribute_macros.h",
         "cxx20_backports.h",
         "offsetof.h",
         "return_value_slot.h",
diff --git a/support/internal/attribute_macros.h b/support/internal/attribute_macros.h
new file mode 100644
index 0000000..6018a8d
--- /dev/null
+++ b/support/internal/attribute_macros.h
@@ -0,0 +1,41 @@
+// 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_ATTRIBUTES_H_
+#define THIRD_PARTY_CRUBIT_SUPPORT_INTERNAL_ATTRIBUTES_H_
+
+// TODO(jeanpierreda): Get a waiver for these, or else a waiver for each caller.
+#define CRUBIT_INTERNAL_ANNOTATE(...) [[clang::annotate(__VA_ARGS__)]]
+#define CRUBIT_INTERNAL_ANNOTATE_TYPE(...) [[clang::annotate_type(__VA_ARGS__)]]
+
+// Unsafe: disables bindings, and reinterprets all uses of this type as `t`.
+//
+// This attribute completely disables automated bindings for the type which it
+// appertains to. All uses of that type are replaced with uses of `t`, which
+// must be a rust type which exists and is guaranteed to be available by that
+// name.
+//
+// This can be applied to a struct, class, or enum.
+//
+// TODO(b/274834739): also support type aliases.
+//
+// For example, this C++ header:
+//
+// ```c++
+// struct CRUBIT_INTERNAL_RUST_TYPE("char") CharT {std::uint32_t c; };
+// CharT foo() { return {0};}
+// ```
+//
+// Becomes this Rust interface:
+//
+// ```rust
+// pub fn foo() -> char;  // returns '\0'
+// ```
+//
+// SAFETY:
+//   If the type is not ABI-compatible with `t`, the behavior is undefined.
+#define CRUBIT_INTERNAL_RUST_TYPE(t) \
+  CRUBIT_INTERNAL_ANNOTATE("crubit_internal_rust_type", t)
+
+#endif  // THIRD_PARTY_CRUBIT_SUPPORT_INTERNAL_ATTRIBUTES_H_
diff --git a/support/rs_std/BUILD b/support/rs_std/BUILD
index 1785f29..a059e28 100644
--- a/support/rs_std/BUILD
+++ b/support/rs_std/BUILD
@@ -14,7 +14,10 @@
     # seems okay - we should be able to assume that Crubit users have a version
     # of Abseil that is relatively recent (although we can't rely on an
     # exact version and/or exact absl/base/options.h).
-    deps = ["@absl//absl/base:core_headers"],
+    deps = [
+        "@absl//absl/base:core_headers",
+        "//support/internal:bindings_support",
+    ],
 )
 
 cc_test(
diff --git a/support/rs_std/rs_char.h b/support/rs_std/rs_char.h
index 8bec775..a13101e 100644
--- a/support/rs_std/rs_char.h
+++ b/support/rs_std/rs_char.h
@@ -9,13 +9,14 @@
 #include <optional>
 
 #include "absl/base/optimization.h"
+#include "support/internal/attribute_macros.h"
 
 namespace rs_std {
 
 // `rs_std::rs_char` is a C++ representation of the `char` type from Rust.
-// `rust_builtin_type_abi_assumptions.md` documents the ABI compatiblity of
+// `rust_builtin_type_abi_assumptions.md` documents the ABI compatibility of
 // these types.
-class rs_char final {
+class CRUBIT_INTERNAL_RUST_TYPE("char") rs_char final {
  public:
   // Creates a default `rs_char` - one that represents ASCII NUL character.
   //