An interesting, yet somewhat unrelated discussion could be had about cases where one might want the type ID to not change when the type changes structurally. There are many structures in many ABI interfaces that are structurally different between releases, yet still binary compatible. This particular characteristic has always been defined adhoc, and I do wonder what a more formal system might mean. This almost feels like the issue of identity in distributed systems.
> There are many structures in many ABI interfaces that are structurally different between releases, yet still binary compatible.
These should be defined in the API as simple newtype wrappers over some basic binary-level type (generally either unsigned binary word or u8 array) with conversions to and from safer higher-level types defined in Rust code, leaving it to the compiler to optimize these to no-ops whenever possible. This ensures that "binary compatible" types also keep the same structural identity, and conversely, that binary incompatible types are automatically detected as well.
Code generation probably works the best here, although it makes the build system more complex. You would have the typeid mappings as an actually existing file that you can commit into your git repo.
Or if your language supports metaprogramming (something like Nim, Zig, or Jai), you could write compile-time code that maps each of your annotated types into deterministically-determined ids. (An example of this in Nim: https://gist.github.com/PhilipWitte/dd6c670fca3baf573490)