The C# Bincode generator chooses reference-type option helpers for an Option<T> where T is emitted as a C# enum. C# enums are value types, so the generated calls violate the helpers' where T : class constraints.
Observed with facet_generate 0.19.0. The same helper-selection logic is present on main at 76a0246604307b92f4d9daa15238e405f85d17c2 (source inspection; I have not run this standalone reproducer against main).
Minimal example
Generate C# with BincodePlugin for these types:
use facet::Facet;
use facet_generate::{
generation::{bincode::BincodePlugin, csharp},
reflection::RegistryBuilder,
};
#[derive(Facet)]
#[repr(C)]
enum ContactGroup {
Align,
Partner,
}
#[derive(Facet)]
struct Filter {
group: Option<ContactGroup>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let registry = RegistryBuilder::new().add_type::<Filter>()?.build()?;
csharp::Installer::new("Example", "generated")
.plugin(BincodePlugin)
.generate(®istry)?;
Ok(())
}
The generated ContactGroup is a C# enum, and the property is nullable (ContactGroup?). However, the generated Bincode calls use the reference helpers (namespace simplified):
FacetHelpers.SerializeOptionRef(Group, serializer,
(item, s) => Example.ContactGroupBincode.Serialize(item, s));
var group = FacetHelpers.DeserializeOptionRef(deserializer,
d => Example.ContactGroupBincode.Deserialize(d));
Those helpers constrain T to class; compiling the generated C# therefore fails for the enum value type.
Expected:
FacetHelpers.SerializeOption(Group, serializer,
(item, s) => Example.ContactGroupBincode.Serialize(item, s));
var group = FacetHelpers.DeserializeOption(deserializer,
d => Example.ContactGroupBincode.Deserialize(d));
The existing value helpers use where T : struct and preserve None as null.
Likely cause and fix
option_serialize_helper / option_deserialize_helper call is_csharp_value_type, which recognizes primitives and tuples but does not resolve Format::TypeName against the generated C-style enum set. The serialization/deserialization expression writers already have c_style_enums available.
Use that type information when selecting option helpers, for both serialization and deserialization. Keep reference helpers for enums emitted as record hierarchies and for other reference types.
Suggested regression coverage
- Compile generated C# for an optional C-style enum.
- Round-trip
None, Some(Align) (the first/zero-valued variant), and Some(Partner) between Rust and C#.
- Cover optional enums inside collections as well as direct fields.
- Retain coverage for optional record-based enums and reference types.
Downstream impact
We encountered this in a Windows app's optional contact-group filter. A previous downstream workaround removed the reference-helper constraint and returned default; that incorrectly collapsed None into the first enum value. That silent corruption was caused by our workaround, not by upstream's unmodified output. Our current workaround selects the correct nullable-value helper, but this belongs in the generator so consumers need no output patching.
The C# Bincode generator chooses reference-type option helpers for an
Option<T>whereTis emitted as a C# enum. C# enums are value types, so the generated calls violate the helpers'where T : classconstraints.Observed with
facet_generate0.19.0. The same helper-selection logic is present on main at76a0246604307b92f4d9daa15238e405f85d17c2(source inspection; I have not run this standalone reproducer against main).Minimal example
Generate C# with
BincodePluginfor these types:The generated
ContactGroupis a C#enum, and the property is nullable (ContactGroup?). However, the generated Bincode calls use the reference helpers (namespace simplified):Those helpers constrain
Ttoclass; compiling the generated C# therefore fails for the enum value type.Expected:
The existing value helpers use
where T : structand preserveNoneasnull.Likely cause and fix
option_serialize_helper/option_deserialize_helpercallis_csharp_value_type, which recognizes primitives and tuples but does not resolveFormat::TypeNameagainst the generated C-style enum set. The serialization/deserialization expression writers already havec_style_enumsavailable.Use that type information when selecting option helpers, for both serialization and deserialization. Keep reference helpers for enums emitted as record hierarchies and for other reference types.
Suggested regression coverage
None,Some(Align)(the first/zero-valued variant), andSome(Partner)between Rust and C#.Downstream impact
We encountered this in a Windows app's optional contact-group filter. A previous downstream workaround removed the reference-helper constraint and returned
default; that incorrectly collapsedNoneinto the first enum value. That silent corruption was caused by our workaround, not by upstream's unmodified output. Our current workaround selects the correct nullable-value helper, but this belongs in the generator so consumers need no output patching.