I don't quite understand this. Idiomatically, in the embedded struct case, big_cleanup() / big_destroy() would call small_cleanup(&big->small) directly, which would itself call apple_destroy(), banana_destroy().
Yes that would be good, but we also may be more tempted to skip the small_cleanup() and instead make `big` manage the memory of big.smol.a and big.smol.b directly, and call apple_destroy(), banana_destroy() directly in small_cleanup(). Arguably, this makes more noise if done through a pointer.
For instance we free `v->arrays.sizes` directly on `free_parse_variable_def(v)`, instead of going through a `free_parse_array_sizes(&v->arrays)` so it is something we have to remember to do if we reuse `v->arrays.sizes` on another structs, or that we have to remember to update if we change `parse_array_sizes` adding other fields that need to be freed.
I am not saying that currently we have to change that, I just wanted to find an example. I understand that there are tradeoffs. Writing more code to prematurely generalize or future-proof is not a good thing (even less for really simple structs like `struct parse_array_sizes`), but I also attempt to avoid having keep track of many places to update when a struct is modified or repurposed.
I don't hate it, it just didn't quite seem like the most obvious thing. If you feel strongly about it I don't mind keeping it a pointer.
No, I also don't feel strongly about it either! In fact in this case I didn't even think it too much. This is just me trying to justify a rule of thumb that I often follow somewhat instinctively.