THRIFT-4953: Missing Field Identifiers

When identifiers are not specified, negative id will be converted to a valid
rust identifier.
diff --git a/compiler/cpp/src/thrift/generate/t_rs_generator.cc b/compiler/cpp/src/thrift/generate/t_rs_generator.cc
index 77ee906..70e1847 100644
--- a/compiler/cpp/src/thrift/generate/t_rs_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_rs_generator.cc
@@ -516,6 +516,10 @@
 
   // Replace all instances of `search_string` with `replace_string` in `target`.
   void string_replace(string& target, const string& search_string, const string& replace_string);
+
+  // Adjust field identifier to correctly handle unspecified field identifiers
+  // THRIFT-4953
+  string rust_safe_field_id(int32_t id);
 };
 
 void t_rs_generator::init_generator() {
@@ -1172,8 +1176,8 @@
         generic_type_parameters << ", ";
         generic_type_qualifiers << ", ";
       }
-      generic_type_parameters << "F" << member->get_key();
-      generic_type_qualifiers << "F" << member->get_key() << ": Into<Option<" << to_rust_type(member->get_type()) << ">>";
+      generic_type_parameters << "F" << rust_safe_field_id(member->get_key());
+      generic_type_qualifiers << "F" << rust_safe_field_id(member->get_key()) << ": Into<Option<" << to_rust_type(member->get_type()) << ">>";
     }
   }
 
@@ -1204,7 +1208,7 @@
     }
 
     if (is_optional(member_req)) {
-      args << member_name << ": " << "F" << member->get_key();
+      args << member_name << ": " << "F" << rust_safe_field_id(member->get_key());
     } else {
       args << member_name << ": " << to_rust_type(member->get_type());
     }
@@ -1720,7 +1724,7 @@
 
   for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) {
     t_field* tfield = (*members_iter);
-    f_gen_ << indent() << tfield->get_key() << " => {" << endl;
+    f_gen_ << indent() << rust_safe_field_id(tfield->get_key()) << " => {" << endl;
     indent_up();
     render_type_sync_read("val", tfield->get_type());
     f_gen_ << indent() << struct_field_read_temp_variable(tfield) << " = Some(val);" << endl;
@@ -1830,7 +1834,7 @@
   vector<t_field*>::const_iterator members_iter;
   for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) {
     t_field* member = (*members_iter);
-    f_gen_ << indent() << member->get_key() << " => {" << endl;
+    f_gen_ << indent() << rust_safe_field_id(member->get_key()) << " => {" << endl;
     indent_up();
     render_type_sync_read("val", member->get_type());
     f_gen_ << indent() << "if ret.is_none() {" << endl;
@@ -2034,7 +2038,7 @@
 
 string t_rs_generator::struct_field_read_temp_variable(t_field* tfield) {
   std::ostringstream foss;
-  foss << "f_" << tfield->get_key();
+  foss << "f_" << rust_safe_field_id(tfield->get_key());
   return foss.str();
 }
 
@@ -3314,6 +3318,17 @@
   return str;
 }
 
+string t_rs_generator::rust_safe_field_id(int32_t id) {
+    string id_str = std::to_string(abs(id));
+    if (id >= 0) {
+        return id_str;
+    } else {
+        string str("neg");
+        str += id_str;
+        return str;
+    }
+}
+
 void t_rs_generator::string_replace(string& target, const string& search_string, const string& replace_string) {
   if (target.empty()) {
     return;
diff --git a/lib/rs/test/Makefile.am b/lib/rs/test/Makefile.am
index 8edd756..486188c 100644
--- a/lib/rs/test/Makefile.am
+++ b/lib/rs/test/Makefile.am
@@ -25,6 +25,7 @@
 	$(THRIFT) -I ./thrifts -out src --gen rs thrifts/Midlayer.thrift
 	$(THRIFT) -I ./thrifts -out src --gen rs thrifts/Ultimate.thrift
 	$(THRIFT) -out src --gen rs $(top_builddir)/test/Recursive.thrift
+	$(THRIFT) -out src --gen rs $(top_builddir)/test/Identifiers.thrift #THRIFT-4953
 
 check: stubs
 	$(CARGO) build
diff --git a/test/Identifiers.thrift b/test/Identifiers.thrift
new file mode 100644
index 0000000..aa3e4ea
--- /dev/null
+++ b/test/Identifiers.thrift
@@ -0,0 +1,6 @@
+// THRIFT-4953
+struct NoFieldIdentifiersTest {
+  string field_without_id1,
+  string field_without_id2,
+  1: string field_with_id
+}
\ No newline at end of file