Change upb singular scalar accessors to not use upb C accessor codegen.

PiperOrigin-RevId: 666879420
diff --git a/rust/upb/lib.rs b/rust/upb/lib.rs
index 125a0f2..a24f78e 100644
--- a/rust/upb/lib.rs
+++ b/rust/upb/lib.rs
@@ -34,19 +34,15 @@
 };
 
 mod message;
-pub use message::{
-    upb_Message, upb_Message_Clear, upb_Message_DeepClone, upb_Message_DeepCopy,
-    upb_Message_IsEqual, upb_Message_MergeFrom, upb_Message_New, upb_Message_SetBaseField,
-    RawMessage,
-};
+pub use message::*;
 
 mod message_value;
 pub use message_value::{upb_MessageValue, upb_MutableMessageValue};
 
 mod mini_table;
 pub use mini_table::{
-    upb_MiniTable, upb_MiniTableField, upb_MiniTable_FindFieldByNumber, RawMiniTable,
-    RawMiniTableField,
+    upb_MiniTable, upb_MiniTableField, upb_MiniTable_FindFieldByNumber,
+    upb_MiniTable_GetFieldByIndex, RawMiniTable, RawMiniTableField,
 };
 
 mod opaque_pointee;
diff --git a/rust/upb/message.rs b/rust/upb/message.rs
index b78e495..caaa80a 100644
--- a/rust/upb/message.rs
+++ b/rust/upb/message.rs
@@ -13,17 +13,22 @@
 pub type RawMessage = NonNull<upb_Message>;
 
 extern "C" {
-    /// SAFETY:
+    /// # Safety
     /// - `mini_table` and `arena` must be valid to deref
     pub fn upb_Message_New(mini_table: *const upb_MiniTable, arena: RawArena)
     -> Option<RawMessage>;
 
-    /// SAFETY:
+    /// # Safety
     /// - `m` and `mini_table` must be valid to deref
-    /// - `mini_table` must be the MiniTable associtaed with `m`
+    /// - `mini_table` must be the MiniTable associated with `m`
     pub fn upb_Message_Clear(m: RawMessage, mini_table: *const upb_MiniTable);
 
-    /// SAFETY:
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `f`
+    pub fn upb_Message_ClearBaseField(m: RawMessage, f: *const upb_MiniTableField);
+
+    /// # Safety
     /// - All four arguments must be valid to deref
     /// - `mini_table` must be the MiniTable associated with both `dst` and
     ///   `src`
@@ -34,7 +39,7 @@
         arena: RawArena,
     );
 
-    /// SAFETY:
+    /// # Safety
     /// - All three arguments must be valid to deref
     /// - `mini_table` must be the MiniTable associated with `m`
     pub fn upb_Message_DeepClone(
@@ -43,18 +48,86 @@
         arena: RawArena,
     ) -> Option<RawMessage>;
 
-    /// SAFETY:
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `f`
+    pub fn upb_Message_GetBool(
+        m: RawMessage,
+        mini_table: *const upb_MiniTableField,
+        default_val: bool,
+    ) -> bool;
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_GetInt32(
+        m: RawMessage,
+        f: *const upb_MiniTableField,
+        default_val: i32,
+    ) -> i32;
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_GetInt64(
+        m: RawMessage,
+        f: *const upb_MiniTableField,
+        default_val: i64,
+    ) -> i64;
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_GetUInt32(
+        m: RawMessage,
+        f: *const upb_MiniTableField,
+        default_val: u32,
+    ) -> u32;
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_GetUInt64(
+        m: RawMessage,
+        f: *const upb_MiniTableField,
+        default_val: u64,
+    ) -> u64;
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_GetFloat(
+        m: RawMessage,
+        f: *const upb_MiniTableField,
+        default_val: f32,
+    ) -> f32;
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_GetDouble(
+        m: RawMessage,
+        f: *const upb_MiniTableField,
+        default_val: f64,
+    ) -> f64;
+
+    /// # Safety
     /// - `m` and `mini_table` must be valid to deref
     /// - `mini_table` must be the MiniTable associated with `m`
+    pub fn upb_Message_HasBaseField(m: RawMessage, mini_table: *const upb_MiniTableField) -> bool;
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
     /// - `val` must be a pointer to legally readable memory of the correct type
     ///   for the field described by `mini_table`
     pub fn upb_Message_SetBaseField(
         m: RawMessage,
-        mini_table: *const upb_MiniTableField,
+        f: *const upb_MiniTableField,
         val: *const std::ffi::c_void,
     );
 
-    /// SAFETY:
+    /// # Safety
     /// - All four arguments must be valid to deref
     /// - `mini_table` must be the MiniTable associated with both `m1` and `m2`
     pub fn upb_Message_IsEqual(
@@ -64,7 +137,7 @@
         options: i32,
     ) -> bool;
 
-    /// SAFETY:
+    /// # Safety
     /// - `dst`, `src`, `mini_table` and `arena` must be valid to deref
     /// - `extreg` must be valid to deref or nullptr
     /// - `mini_table` must be the MiniTable associated with both `dst` and
@@ -76,4 +149,43 @@
         extreg: *const upb_ExtensionRegistry,
         arena: RawArena,
     ) -> bool;
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `f`
+    pub fn upb_Message_SetBaseFieldBool(
+        m: RawMessage,
+        mini_table: *const upb_MiniTableField,
+        val: bool,
+    );
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_SetBaseFieldInt32(m: RawMessage, f: *const upb_MiniTableField, val: i32);
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_SetBaseFieldInt64(m: RawMessage, f: *const upb_MiniTableField, val: i64);
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_SetBaseFieldUInt32(m: RawMessage, f: *const upb_MiniTableField, val: u32);
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_SetBaseFieldUInt64(m: RawMessage, f: *const upb_MiniTableField, val: u64);
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_SetBaseFieldFloat(m: RawMessage, f: *const upb_MiniTableField, val: f32);
+
+    /// # Safety
+    /// - `m` and `mini_table` must be valid to deref
+    /// - `f` must be a field associated with `m`
+    pub fn upb_Message_SetBaseFieldDouble(m: RawMessage, f: *const upb_MiniTableField, val: f64);
 }
diff --git a/rust/upb/mini_table.rs b/rust/upb/mini_table.rs
index 64b64ec..cfc19c8 100644
--- a/rust/upb/mini_table.rs
+++ b/rust/upb/mini_table.rs
@@ -19,4 +19,9 @@
         m: *const upb_MiniTable,
         number: u32,
     ) -> *const upb_MiniTableField;
+
+    pub fn upb_MiniTable_GetFieldByIndex(
+        m: *const upb_MiniTable,
+        number: u32,
+    ) -> *const upb_MiniTableField;
 }
diff --git a/src/google/protobuf/compiler/rust/BUILD.bazel b/src/google/protobuf/compiler/rust/BUILD.bazel
index 5ff8f51..fd28df6 100644
--- a/src/google/protobuf/compiler/rust/BUILD.bazel
+++ b/src/google/protobuf/compiler/rust/BUILD.bazel
@@ -86,11 +86,11 @@
         ":enum",
         ":naming",
         ":oneof",
+        ":upb_helpers",
         "//src/google/protobuf",
         "//src/google/protobuf/compiler/cpp:names",
         "//src/google/protobuf/compiler/cpp:names_internal",
         "//src/google/protobuf/compiler/rust/accessors",
-        "//upb_generator:mangle",
         "@com_google_absl//absl/log:absl_check",
         "@com_google_absl//absl/log:absl_log",
         "@com_google_absl//absl/strings",
@@ -235,3 +235,18 @@
         "@com_google_absl//absl/log:absl_log",
     ],
 )
+
+cc_library(
+    name = "upb_helpers",
+    srcs = ["upb_helpers.cc"],
+    hdrs = ["upb_helpers.h"],
+    strip_include_prefix = "/src",
+    visibility = [
+        "//src/google/protobuf/compiler/rust:__subpackages__",
+    ],
+    deps = [
+        "//src/google/protobuf",
+        "//upb_generator:mangle",
+        "@com_google_absl//absl/log:absl_check",
+    ],
+)
diff --git a/src/google/protobuf/compiler/rust/accessors/BUILD.bazel b/src/google/protobuf/compiler/rust/accessors/BUILD.bazel
index 15bc15f..f5ff57e 100644
--- a/src/google/protobuf/compiler/rust/accessors/BUILD.bazel
+++ b/src/google/protobuf/compiler/rust/accessors/BUILD.bazel
@@ -37,6 +37,7 @@
         "//src/google/protobuf/compiler/rust:context",
         "//src/google/protobuf/compiler/rust:naming",
         "//src/google/protobuf/compiler/rust:rust_field_type",
+        "//src/google/protobuf/compiler/rust:upb_helpers",
         "//src/google/protobuf/io:tokenizer",
         "@com_google_absl//absl/log:absl_check",
         "@com_google_absl//absl/log:absl_log",
diff --git a/src/google/protobuf/compiler/rust/accessors/default_value.cc b/src/google/protobuf/compiler/rust/accessors/default_value.cc
index 27594b3..c31bf75 100644
--- a/src/google/protobuf/compiler/rust/accessors/default_value.cc
+++ b/src/google/protobuf/compiler/rust/accessors/default_value.cc
@@ -58,13 +58,13 @@
         ABSL_LOG(FATAL) << "unreachable";
       }
     case RustFieldType::INT32:
-      return absl::StrFormat("%d", field.default_value_int32());
+      return absl::StrFormat("%di32", field.default_value_int32());
     case RustFieldType::INT64:
-      return absl::StrFormat("%d", field.default_value_int64());
+      return absl::StrFormat("%di64", field.default_value_int64());
     case RustFieldType::UINT64:
-      return absl::StrFormat("%u", field.default_value_uint64());
+      return absl::StrFormat("%uu64", field.default_value_uint64());
     case RustFieldType::UINT32:
-      return absl::StrFormat("%u", field.default_value_uint32());
+      return absl::StrFormat("%uu32", field.default_value_uint32());
     case RustFieldType::BOOL:
       return absl::StrFormat("%v", field.default_value_bool());
     case RustFieldType::STRING:
diff --git a/src/google/protobuf/compiler/rust/accessors/repeated_field.cc b/src/google/protobuf/compiler/rust/accessors/repeated_field.cc
index 797ac8f..04e638e 100644
--- a/src/google/protobuf/compiler/rust/accessors/repeated_field.cc
+++ b/src/google/protobuf/compiler/rust/accessors/repeated_field.cc
@@ -13,6 +13,7 @@
 #include "google/protobuf/compiler/rust/accessors/generator.h"
 #include "google/protobuf/compiler/rust/context.h"
 #include "google/protobuf/compiler/rust/naming.h"
+#include "google/protobuf/compiler/rust/upb_helpers.h"
 #include "google/protobuf/descriptor.h"
 
 namespace google {
@@ -110,12 +111,13 @@
                return;
              }
              if (ctx.is_upb()) {
-               ctx.Emit({{"field_number", field.number()}}, R"rs(
+               ctx.Emit({{"field_index", UpbMiniTableFieldIndex(field)}},
+                        R"rs(
                     pub fn set_$raw_field_name$(&mut self, src: impl $pb$::IntoProxied<$pb$::Repeated<$RsType$>>) {
                       let minitable_field = unsafe {
-                        $pbr$::upb_MiniTable_FindFieldByNumber(
+                        $pbr$::upb_MiniTable_GetFieldByIndex(
                           <Self as $pbr$::AssociatedMiniTable>::mini_table(),
-                          $field_number$
+                          $field_index$
                         )
                       };
                       let val = src.into_proxied($pbi$::Private);
diff --git a/src/google/protobuf/compiler/rust/accessors/singular_scalar.cc b/src/google/protobuf/compiler/rust/accessors/singular_scalar.cc
index a3d5c62..904f36d 100644
--- a/src/google/protobuf/compiler/rust/accessors/singular_scalar.cc
+++ b/src/google/protobuf/compiler/rust/accessors/singular_scalar.cc
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "absl/log/absl_check.h"
 #include "absl/strings/string_view.h"
 #include "google/protobuf/compiler/cpp/helpers.h"
 #include "google/protobuf/compiler/rust/accessors/accessor_case.h"
@@ -14,6 +15,7 @@
 #include "google/protobuf/compiler/rust/accessors/generator.h"
 #include "google/protobuf/compiler/rust/context.h"
 #include "google/protobuf/compiler/rust/naming.h"
+#include "google/protobuf/compiler/rust/upb_helpers.h"
 #include "google/protobuf/descriptor.h"
 
 namespace google {
@@ -21,9 +23,43 @@
 namespace compiler {
 namespace rust {
 
+namespace {
+
+// The upb function to use for the get/set functions, eg `Int32` for the
+// functions `upb_Message_GetInt32` and upb_Message_SetInt32`.
+std::string UpbCTypeNameForFunctions(const FieldDescriptor& field) {
+  switch (field.cpp_type()) {
+    case FieldDescriptor::CPPTYPE_INT32:
+      return "Int32";
+    case FieldDescriptor::CPPTYPE_INT64:
+      return "Int64";
+    case FieldDescriptor::CPPTYPE_UINT32:
+      return "UInt32";
+    case FieldDescriptor::CPPTYPE_UINT64:
+      return "UInt64";
+    case FieldDescriptor::CPPTYPE_DOUBLE:
+      return "Double";
+    case FieldDescriptor::CPPTYPE_FLOAT:
+      return "Float";
+    case FieldDescriptor::CPPTYPE_BOOL:
+      return "Bool";
+    case FieldDescriptor::CPPTYPE_ENUM:
+      return "Int32";
+    case FieldDescriptor::CPPTYPE_STRING:
+    case FieldDescriptor::CPPTYPE_MESSAGE:
+      // Handled by a different file.
+      break;
+  }
+  ABSL_CHECK(false) << "Unexpected field type: " << field.cpp_type_name();
+  return "";
+}
+
+}  // namespace
+
 void SingularScalar::InMsgImpl(Context& ctx, const FieldDescriptor& field,
                                AccessorCase accessor_case) const {
   std::string field_name = FieldNameWithCollisionAvoidance(field);
+
   ctx.Emit(
       {
           {"field", RsSafeName(field_name)},
@@ -32,13 +68,37 @@
           {"Scalar", RsTypePath(ctx, field)},
           {"hazzer_thunk", ThunkName(ctx, field, "has")},
           {"default_value", DefaultValue(ctx, field)},
+          {"upb_mt_field_index", UpbMiniTableFieldIndex(field)},
+          {"upb_fn_type_name", UpbCTypeNameForFunctions(field)},
           {"getter",
            [&] {
-             ctx.Emit(R"rs(
-                  pub fn $field$($view_self$) -> $Scalar$ {
-                    unsafe { $getter_thunk$(self.raw_msg()) }
-                  }
-                )rs");
+             if (ctx.is_cpp()) {
+               ctx.Emit(R"rs(
+                    pub fn $field$($view_self$) -> $Scalar$ {
+                      unsafe { $getter_thunk$(self.raw_msg()) }
+                    }
+                  )rs");
+             } else {
+               ctx.Emit(
+                   R"rs(
+                    pub fn $field$($view_self$) -> $Scalar$ {
+                      unsafe {
+                        let mt = <Self as $pbr$::AssociatedMiniTable>::mini_table();
+                        let f = $pbr$::upb_MiniTable_GetFieldByIndex(
+                            mt, $upb_mt_field_index$);
+
+                        // TODO: b/361751487: This .into() and .try_into() is only
+                        // here for the enum<->i32 case, we should avoid it for
+                        // other primitives where the types naturally match
+                        // perfectly (and do an unchecked conversion for
+                        // i32->enum types, since even for closed enums we trust
+                        // upb to only return one of the named values).
+                        $pbr$::upb_Message_Get$upb_fn_type_name$(
+                            self.raw_msg(), f, ($default_value$).into()).try_into().unwrap()
+                      }
+                    }
+                  )rs");
+             }
            }},
           {"getter_opt",
            [&] {
@@ -56,28 +116,70 @@
           {"setter",
            [&] {
              if (accessor_case == AccessorCase::VIEW) return;
-             ctx.Emit({}, R"rs(
-                 pub fn set_$raw_field_name$(&mut self, val: $Scalar$) {
-                   unsafe { $setter_thunk$(self.raw_msg(), val) }
-                 }
-               )rs");
+             if (ctx.is_cpp()) {
+               ctx.Emit(R"rs(
+                  pub fn set_$raw_field_name$(&mut self, val: $Scalar$) {
+                    unsafe { $setter_thunk$(self.raw_msg(), val) }
+                  }
+                )rs");
+             } else {
+               ctx.Emit(R"rs(
+                  pub fn set_$raw_field_name$(&mut self, val: $Scalar$) {
+                    unsafe {
+                      let mt = <Self as $pbr$::AssociatedMiniTable>::mini_table();
+                      let f = $pbr$::upb_MiniTable_GetFieldByIndex(
+                          mt, $upb_mt_field_index$);
+                      // TODO: b/361751487: This .into() is only here
+                      // here for the enum<->i32 case, we should avoid it for
+                      // other primitives where the types naturally match
+                      // perfectly.
+                      $pbr$::upb_Message_SetBaseField$upb_fn_type_name$(
+                          self.raw_msg(), f, val.into());
+                    }
+                  }
+                )rs");
+             }
            }},
           {"hazzer",
            [&] {
              if (!field.has_presence()) return;
-             ctx.Emit({}, R"rs(
-                pub fn has_$raw_field_name$($view_self$) -> bool {
-                  unsafe { $hazzer_thunk$(self.raw_msg()) }
-                })rs");
+             if (ctx.is_cpp()) {
+               ctx.Emit(R"rs(
+                  pub fn has_$raw_field_name$($view_self$) -> bool {
+                    unsafe { $hazzer_thunk$(self.raw_msg()) }
+                  })rs");
+             } else {
+               ctx.Emit(R"rs(
+                  pub fn has_$raw_field_name$($view_self$) -> bool {
+                    unsafe {
+                      let mt = <Self as $pbr$::AssociatedMiniTable>::mini_table();
+                      let f = $pbr$::upb_MiniTable_GetFieldByIndex(
+                          mt, $upb_mt_field_index$);
+                      $pbr$::upb_Message_HasBaseField(self.raw_msg(), f)
+                    }
+                  })rs");
+             }
            }},
           {"clearer",
            [&] {
              if (accessor_case == AccessorCase::VIEW) return;
              if (!field.has_presence()) return;
-             ctx.Emit({}, R"rs(
-                  pub fn clear_$raw_field_name$(&mut self) {
-                    unsafe { $clearer_thunk$(self.raw_msg()) }
-                  })rs");
+             if (ctx.is_cpp()) {
+               ctx.Emit(R"rs(
+                    pub fn clear_$raw_field_name$(&mut self) {
+                      unsafe { $clearer_thunk$(self.raw_msg()) }
+                    })rs");
+             } else {
+               ctx.Emit(R"rs(
+                    pub fn clear_$raw_field_name$(&mut self) {
+                      unsafe {
+                        let mt = <Self as $pbr$::AssociatedMiniTable>::mini_table();
+                        let f = $pbr$::upb_MiniTable_GetFieldByIndex(
+                            mt, $upb_mt_field_index$);
+                        $pbr$::upb_Message_ClearBaseField(self.raw_msg(), f);
+                      }
+                    })rs");
+             }
            }},
           {"getter_thunk", ThunkName(ctx, field, "get")},
           {"setter_thunk", ThunkName(ctx, field, "set")},
@@ -94,6 +196,9 @@
 
 void SingularScalar::InExternC(Context& ctx,
                                const FieldDescriptor& field) const {
+  // Only cpp kernel uses thunks.
+  if (ctx.is_upb()) return;
+
   // In order to soundly pass a Rust type to C/C++ as a function argument,
   // the types must be FFI-compatible.
   // This requires special consideration for enums, which aren't trivial
diff --git a/src/google/protobuf/compiler/rust/message.cc b/src/google/protobuf/compiler/rust/message.cc
index be85bff..a7d119b 100644
--- a/src/google/protobuf/compiler/rust/message.cc
+++ b/src/google/protobuf/compiler/rust/message.cc
@@ -21,8 +21,8 @@
 #include "google/protobuf/compiler/rust/enum.h"
 #include "google/protobuf/compiler/rust/naming.h"
 #include "google/protobuf/compiler/rust/oneof.h"
+#include "google/protobuf/compiler/rust/upb_helpers.h"
 #include "google/protobuf/descriptor.h"
-#include "upb_generator/mangle.h"
 
 namespace google {
 namespace protobuf {
@@ -30,10 +30,6 @@
 namespace rust {
 namespace {
 
-std::string UpbMinitableName(const Descriptor& msg) {
-  return upb::generator::MessageInit(msg.full_name());
-}
-
 void MessageNew(Context& ctx, const Descriptor& msg) {
   switch (ctx.opts().kernel) {
     case Kernel::kCpp:
@@ -75,7 +71,7 @@
       return;
 
     case Kernel::kUpb:
-      ctx.Emit({{"minitable", UpbMinitableName(msg)}},
+      ctx.Emit({{"minitable", UpbMiniTableName(msg)}},
                R"rs(
         // SAFETY: `MINI_TABLE` is the one associated with `self.raw_msg()`.
         let encoded = unsafe {
@@ -132,8 +128,8 @@
       return;
 
     case Kernel::kUpb:
-      ctx.Emit({{"minitable", UpbMinitableName(msg)}},
-               R"rs(
+      ctx.Emit(
+          R"rs(
         let mut msg = Self::new();
 
         // SAFETY:
@@ -227,7 +223,7 @@
       ctx.Emit(
           {
               {"new_thunk", ThunkName(ctx, msg, "new")},
-              {"minitable", UpbMinitableName(msg)},
+              {"minitable", UpbMiniTableName(msg)},
           },
           R"rs(
           fn $new_thunk$(arena: $pbr$::RawArena) -> $pbr$::RawMessage;
@@ -274,7 +270,7 @@
       return;
 
     case Kernel::kUpb:
-      ctx.Emit({{"minitable", UpbMinitableName(msg)}}, R"rs(
+      ctx.Emit(R"rs(
         impl<'msg> $pb$::IntoProxied<$Msg$> for $Msg$View<'msg> {
           fn into_proxied(self, _private: $pbi$::Private) -> $Msg$ {
             let dst = $Msg$::new();
@@ -302,7 +298,7 @@
 
 void UpbGeneratedMessageTraitImpls(Context& ctx, const Descriptor& msg) {
   if (ctx.opts().kernel == Kernel::kUpb) {
-    ctx.Emit({{"minitable", UpbMinitableName(msg)}}, R"rs(
+    ctx.Emit({{"minitable", UpbMiniTableName(msg)}}, R"rs(
       impl $pbr$::AssociatedMiniTable for $Msg$ {
         #[inline(always)]
         unsafe fn mini_table() -> *const $pbr$::upb_MiniTable {
diff --git a/src/google/protobuf/compiler/rust/upb_helpers.cc b/src/google/protobuf/compiler/rust/upb_helpers.cc
new file mode 100644
index 0000000..c573599
--- /dev/null
+++ b/src/google/protobuf/compiler/rust/upb_helpers.cc
@@ -0,0 +1,40 @@
+#include "google/protobuf/compiler/rust/upb_helpers.h"
+
+#include <cstdint>
+#include <string>
+
+#include "absl/log/absl_check.h"
+#include "google/protobuf/descriptor.h"
+#include "upb_generator/mangle.h"
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace rust {
+
+std::string UpbMiniTableName(const Descriptor& msg) {
+  return upb::generator::MessageInit(msg.full_name());
+}
+
+uint32_t UpbMiniTableFieldIndex(const FieldDescriptor& field) {
+  auto* parent = field.containing_type();
+  ABSL_CHECK(parent != nullptr);
+
+  // TODO: b/361751487 - We should get the field_index from
+  // UpbDefs directly, instead of independently matching
+  // the sort order here.
+
+  uint32_t num_fields_with_lower_field_number = 0;
+  for (int i = 0; i < parent->field_count(); ++i) {
+    if (parent->field(i)->number() < field.number()) {
+      ++num_fields_with_lower_field_number;
+    }
+  }
+
+  return num_fields_with_lower_field_number;
+}
+
+}  // namespace rust
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google
diff --git a/src/google/protobuf/compiler/rust/upb_helpers.h b/src/google/protobuf/compiler/rust/upb_helpers.h
new file mode 100644
index 0000000..312afbf
--- /dev/null
+++ b/src/google/protobuf/compiler/rust/upb_helpers.h
@@ -0,0 +1,32 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2024 Google LLC.  All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#ifndef GOOGLE_PROTOBUF_COMPILER_RUST_UPB_HELPERS_H__
+#define GOOGLE_PROTOBUF_COMPILER_RUST_UPB_HELPERS_H__
+
+#include <cstdint>
+#include <string>
+
+#include "google/protobuf/descriptor.h"
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace rust {
+
+// The symbol name for the MiniTable generated by upb MiniTable C codegen.
+std::string UpbMiniTableName(const Descriptor& msg);
+
+// The field index that the provided field will be in a upb_MiniTable.
+uint32_t UpbMiniTableFieldIndex(const FieldDescriptor& field);
+
+}  // namespace rust
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // GOOGLE_PROTOBUF_COMPILER_RUST_UPB_HELPERS_H__