Iterating over struct properties with complex types

What's a nice way to iterate over struct properties?

I've currently got a function that uses serde to convert to a map:

pub fn struct_to_map<T>(data: T) -> std::collections::BTreeMap<Value, Value>
where
    T: Serialize,
{
    let map: std::collections::BTreeMap<Value, Value> = match serde_value::to_value(data) {
        Ok(Value::Map(map)) => map,
        _ => panic!("expected a struct"),
    };
    map
}

But I'm finding iterating over the properties to be quite confusing when I need to keep the types of the values, especially when the types are enums (or nested enums), or other more complicated types.

I'm trying to do stuff like match on certain enum variants like:

pub enum Omittable<T> {
    Keep(T),
    Omit,
}

pub enum Colset<T> {
    Select(T),
    AlwaysSet(T),
} 

let upsert = generated::upsert_configs::Upsert_test_nobackup__test_table {
    id: Colset::Select(Omittable::Keep(Uuid::new_v4())),
    bool_col: Colset::AlwaysSet(Some(true)),
    ci_array: Colset::AlwaysSet(vec!["blah".to_string()]),
    ci_col: Colset::AlwaysSet(None),
    comment: Colset::AlwaysSet(Some("blah".to_string())),
    not_case_sensitive: Colset::AlwaysSet(Some("blah".to_string())),
};

let upsert_map = util::util_struct::struct_to_map(upsert);

for (key, value) in upsert_map.iter() {
    println!("key {:?} = {:?}", key, value);
    match (value) {
        serde_value::Value::Map(Map(String("AlwaysSet"), _)) => { // <---- confused here
            println!("SELECT ON: {:?}", key)
        }
        _ => {
            println!("default: {:?}", key);
        }
    }
}

The "confused here" line above is where I can't figure out the syntax. I've tried all sorts of things, but get errors like:

error: expected tuple struct or tuple variant, found struct `Map`

I know it's wrong, but not sure where to go from here.

When running, I see some println output like:

key String("bool_col") = Map({String("AlwaysSet"): Option(Some(Bool(true)))})
key String("ci_array") = Map({String("AlwaysSet"): Seq([String("blah")])})
key String("ci_col") = Map({String("AlwaysSet"): Option(None)})
key String("comment") = Map({String("AlwaysSet"): Option(Some(String("blah")))})
key String("id") = Map({String("InsertOnly"): String("Omit")})
key String("not_case_sensitive") = Map({String("AlwaysSet"): Option(Some(String("blah")))})

Which I'm having trouble translating into match() arms.

Is there some simpler way to loop over struct properties and keep the regular value types without all the serde / "Value" stuff? Or is this the only way to do it in Rust?

If it's the only way, keen for any info on grokking how I do the pattern matching on my nested enums and other complicated/nested types.

No, there's nothing built-in into Rust. Rust isn't a dynamic language, so in the compiled program struct names and their fields don't exist. All types are compiled to just being anonymous bunch of bytes and offsets without any way to iterate over them — unless you write your own code that does that, like serde derive.

1 Like