Hey guys a newbie question here. I am writing a library that calls a remote JSON API and needs to (un)marshal polymorphic results. I use traits to represent polymorphism. I devised an algorithm to matching the type discriminator values coming form the wire that from limited benchmarks seems to outperform HashMap and simple match statement.
I made all the code generation work. To my dismay the generated code cannot be called as it causes stack overflow on entry into my 20K line function.
Why would a function with nested match and if statements overflow the stack on entry? Is there good way to avoid this?
My function goes like this
fn deserialize_object<'de, A: de::MapAccess<'de>>(type_name: &str, map: A) -> Result<VimAny, DeserializationError<A, A::Error>> {
match type_name.len() {
2 => {
if type_name == "ID" {
let ds = de::value::MapAccessDeserializer::new(map);
let obj: Id = de::Deserialize::deserialize(ds)?;
Ok(VimAny::Object(Box::new(obj)))
} else { Err(DeserializationError::UnknownVariant(map)) }
}
3 => {
if type_name == "Tag" {
let ds = de::value::MapAccessDeserializer::new(map);
let obj: Tag = de::Deserialize::deserialize(ds)?;
Ok(VimAny::Object(Box::new(obj)))
} else { Err(DeserializationError::UnknownVariant(map)) }
}
// the above repeats for all length i.e. up to 63 or so.
// Some lengths have many variants and I use match like so
6 => {
match type_name {
"Action" => {
let ds = de::value::MapAccessDeserializer::new(map);
let obj: Action = de::Deserialize::deserialize(ds)?;
Ok(VimAny::Object(Box::new(obj)))
}
// Some lengths have many many options and I first match on a fixed group of symbols
7 => {
let s = &type_name[0..4];
let Some(type_ord) = to_u32(s) else {
return Err(DeserializationError::PassThruError(de::Error::custom("Internal Error: Cannot convert to unsigned int")));
};
match type_ord {
0x4556434d => { // EVCM
if type_name == "EVCMode" {
let ds = de::value::MapAccessDeserializer::new(map);
let obj: EvcMode = de::Deserialize::deserialize(ds)?;
Ok(VimAny::Object(Box::new(obj)))
} else { Err(DeserializationError::UnknownVariant(map)) }
},
0x4576656e => { // Even
if type_name == "EventEx" {
let ds = de::value::MapAccessDeserializer::new(map);
let obj: EventEx = de::Deserialize::deserialize(ds)?;
Ok(VimAny::Object(Box::new(obj)))
} else { Err(DeserializationError::UnknownVariant(map)) }
},
// it ends like so. It is peculiar that I return the map ownership back so it can be used in fallback
_ => { Err(DeserializationError::UnknownVariant(map)) }
}
}
So few questions:
- How can I see what Rust is trying to put on the stack when entering my function? I do not have local variables on top level
- Do you see anything specifically wrong with the above code?
- Any advice how to debug and address the above.
- Do you think partitioning the generated function to say one per length cluster will alleviate the issue. I have the same code with just 4 options not 3500 and it works fine.
In my prior iteration I have similar code that uses HashMap with many small functions like the code in the individual match arms. It is working ok and may be few times slower then I hope the new code will do.
I also want to shift serialization and inter trait cast functions to use match statements too. Thus it is important for me to understand why big match statements cause havoc on the stack before I spend couple of days writing code generator..