12 ms·
One nasty class of errors in C and C++ is due to environment mismatches in separate compilation. For example, a struct may conditionally add a field according t
by ridiculous_fish 9y ago
One nasty class of errors in C and C++ is due to environment mismatches in separate compilation. For example, a struct may conditionally add a field according to some compilation flag, say, whether ASAN is enabled. If compilation units disagree on that flag, they can disagree on the size of the struct, leading to UB.
Does Rust have a way to prevent this class of errors? What ensures that all units have the same view of a type?
- Rusky 9y agoMultiple units don't have their own copies of type definitions to begin with. The compiler sees them all at once instead, to the point that different versions of a crate produce different incompatible types. At a lower level, symbols are mangled to include a crate identification hash. This is more of a way to allow multiple versions to coexist though, as the problem is already solved by that point.
- ridiculous_fish 9y agoThanks for the reply. What do you mean by "different versions of a crate?" Here's a scenario: 1. Compile a library libA that references type T in CrateX. 2. Add a field to T, and recompile CrateX. 3. Compile an executable a.out that passes a T to libA. Nobody bumped CrateX's version or recompiled libA. Won't this crash? Or does this produce a "different version" of CrateX and so it will refuse to link?
- pcwalton 9y agoIt produces a crate with incompatible symbol names (a "different version"). There's a "strict version hash" derived by hashing a representation of the structure of a crate's entire external interface. That hash value is appended to every symbol name as part of the name mangling process.
- comex 9y agoIs that actually true anymore? I just tried compiling a simple crate: pub struct S { a: usize, } pub fn foo(s: &S) -> usize { s.a } …as a dylib, then added another field to S and recompiled. I expected it to change the symbol names (as shown by nm), but it didn't. The metadata might be different (I don't know how to display it), but the dynamic linker doesn't know about that.
- eddyb 9y agoThat changed with incremental recompilation, to allow reuse across changes. However, Cargo will still ensure different versions of the same crate have different symbols, by passing its own hashes to rustc via -C metadata.
- ridiculous_fish 9y agoAh here we go again! What does "different version" mean? Is it explicit version metadata or something computed from the interface?
- Manishearth 9y agoComputed from the interface. The metadata does not understand semver or any of the higher level versioning tools Cargo exposes to users. 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.
- comex 9y ago
- eddyb 9y agoOkay I just properly took a look at this, and some of the sibling responses are only partially correct. It's not symbol names that change, but the crate does appear to have a hash used to sanity-check against this. I used these sources: // a.rs #![crate_type = "lib"] pub struct Foo { pub x: i32 } // b.rs #![crate_type = "lib"] extern crate a; pub fn foo() -> a::Foo { a::Foo {} } // c.rs extern crate b; fn main() { println!("{}", b::foo().x); } The field `x` from crate `a` was originally not there, and added after compiling crate `b`. The error when trying to compile crate `c` was: error[E0460]: found possibly newer version of crate `a` which `b` depends on --> c.rs:1:1 | 1 | extern crate b; | ^^^^^^^^^^^^^^^ | = note: perhaps that crate needs to be recompiled? = note: crate `a` path #1: liba.rlib = note: crate `b` path #1: libb.rlib
- pornel 9y agoIf you interface with C, then Rust doesn't have anything to prevent this class of errors. If you interface Rust with Rust, then Rust weasels out of solving this problem by declaring it doesn't have a stable ABI :) In practice you compile everything with Cargo and it ensures all affected crates are recompiled when interfaces or settings change, so everything matches.