However, Cargo will still ensure different versions of the same crate have different symbols, by passing its own hashes to rustc via -C metadata.
However, Cargo will still ensure different versions of the same crate have different symbols, by passing its own hashes to rustc via -C metadata.
The metadata just contains info on all the types, and their hashes (or something like that), so if stuff doesn't match you'll know.
This is generally visible from the "expected type Foo but found type Foo" error, which will often mention you have two versions of the same crate.
Worth mentioning that unlike C++ or C Rust doesn't have a global name mangling scheme; so it is totally ok for two crates to have a toplevel struct Foo (unlike in C++ where you are forced to namespace them with uniquely-named namespaces). This has the side effect of it being totally ok to link two versions of the same crate together; and Rust will just complain if you try to mix the types.
Distinguishing between crates is done solely through their name and -C metadata values provided by Cargo.
Once crates are loaded by the compiler based on either their explicit path (via --extern) or by being a dependency of another dependency (and there the name and -C metadata prevent collisions), "two versions of the same crate" appears no different than "two different crates".
The Rust compiler tends to "index" information (e.g. turning strings into various IDs) as quickly as possible, so a lot more semantics are "by identity" than "by syntax", and that helps when multiple identities may share a name.
That includes compiling against already compiled crates, instead of header files you have serialized semantic types and functions, which all use proper identities to "name" anything they use in turn - you never have to be looking for a definition, or risk using the wrong one.