The problem here is that all variants (Cat, Dog, Snake) have to be known when defining the interface (Animal). That makes it not extensible. I prefer Rust's traits.
well, that's an innate feature of comptime expansion (or "macro" if you will) and as such this doesn't inhabit the same solution space as dynamic-dispatch interfaces. Benefit of this is it's boiling down to a statically known jump table and thus can be optimized accordingly, ideally boiling down to a direct inline in respective caller paths. The cost for this is whole-program compilation each time. It's a different trade-off, for sure.
Rust traits do not require dynamic dispatch nor a jump table. Therefore rust traits are inherently faster than zig unions.
They can be used via dynamic dispatch but this is not the default.
You just wouldn't use a union if you wanted monomorphization and didn't want e.g. a heterogeneous collection. Even when it is possible, "monomorphize everything" isn't always faster though, because for example ITLBs are not infinitely large.
Yes, it does seem like something the compiler should automatically define for you.