Skip to content

Commit 61e19a6

Browse files
committed
THRIFT-XXXX: Rust codegen: forward-compatible union deserialization in structs
When a struct field has a union type, the generated Rust deserializer now catches 'received empty union' errors from unknown variants and treats them as None instead of propagating the error. Previously, if a Thrift union received a field ID not present in the local IDL, the union deserializer returned Err('received empty union'), which cascaded up and caused the entire parent struct deserialization to fail. This broke forward compatibility. The fix wraps union field reads in struct deserializers with a match that catches the specific 'received empty union' error and silently skips the field. The struct field (already Option<UnionType>) remains None, matching the behavior as if the field were absent. All other errors (malformed data, I/O failures) still propagate normally. Handles both direct union fields and Box<Union> (forward typedef) fields by resolving the type before generating the method call. This aligns Rust with Java, Go, Python, and C++ Thrift behavior.
1 parent 6f18285 commit 61e19a6

1 file changed

Lines changed: 29 additions & 2 deletions

File tree

compiler/cpp/src/thrift/generate/t_rs_generator.cc

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,10 +1699,37 @@ void t_rs_generator::render_struct_sync_read(const string& struct_name,
16991699

17001700
for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) {
17011701
t_field* tfield = (*members_iter);
1702+
t_type* resolved = get_true_type(tfield->get_type());
1703+
bool is_union_field = resolved->is_struct() && ((t_struct*)resolved)->is_union();
17021704
f_gen_ << indent() << rust_safe_field_id(tfield->get_key()) << " => {" << '\n';
17031705
indent_up();
1704-
render_type_sync_read("val", tfield->get_type());
1705-
f_gen_ << indent() << struct_field_read_temp_variable(tfield) << " = Some(val);" << '\n';
1706+
if (is_union_field) {
1707+
// Union fields: catch "received empty union" errors from unknown
1708+
// variants and treat them as None for forward compatibility.
1709+
// This matches how Java, Go, and Python Thrift handle unknown
1710+
// union fields -- they skip silently instead of failing.
1711+
//
1712+
// Use the resolved (non-Box) type for the method call since
1713+
// Box<T>::method() is not valid Rust syntax for turbofish.
1714+
string resolved_type = to_rust_type(resolved);
1715+
bool is_boxed_union = to_rust_type(tfield->get_type()) != resolved_type;
1716+
string read_call(resolved_type + "::read_from_in_protocol(i_prot)");
1717+
string val_expr = is_boxed_union ? "Box::new(val)" : "val";
1718+
f_gen_ << indent() << "match " << read_call << " {" << '\n';
1719+
indent_up();
1720+
f_gen_ << indent() << "Ok(val) => { " << struct_field_read_temp_variable(tfield) << " = Some(" << val_expr << "); }," << '\n';
1721+
f_gen_ << indent() << "Err(thrift::Error::Protocol(ref e)) if e.message.contains(\"received empty union\") => {" << '\n';
1722+
indent_up();
1723+
f_gen_ << indent() << "// forward compatibility: unknown union variant skipped" << '\n';
1724+
indent_down();
1725+
f_gen_ << indent() << "}," << '\n';
1726+
f_gen_ << indent() << "Err(e) => return Err(e)," << '\n';
1727+
indent_down();
1728+
f_gen_ << indent() << "}" << '\n';
1729+
} else {
1730+
render_type_sync_read("val", tfield->get_type());
1731+
f_gen_ << indent() << struct_field_read_temp_variable(tfield) << " = Some(val);" << '\n';
1732+
}
17061733
indent_down();
17071734
f_gen_ << indent() << "}," << '\n';
17081735
}

0 commit comments

Comments
 (0)