Hacker News new | past | comments | ask | show | jobs | submit login

Thanks 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?




Okay 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


It 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.


Is 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.


That 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.


Ah here we go again! What does "different version" mean? Is it explicit version metadata or something computed from the interface?


In that specific context, crates that have the same name within a Cargo crate graph but aren't the same exact crate - this is pretty much always about semver versions, when incompatible ones are required from different parts of the crate graph, you can end up with e.g. serde-1.0 and serde-0.9, and they get compiled with different -C metadata values, into which "1.0" and "0.9" were factored in.


Computed 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.


You sure you meant to reply to the follow-up to my comment? None of the hashes you mentioned is actually used by the compiler, except to detect changes wrt incremental recompilation. (EDIT: and to catch recompiled crates, see: https://news.ycombinator.com/item?id=16004250)

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.


Are you sure the metadata hash that Cargo computes (and passes with -C metadata) is based on the AST? I don’t think Cargo tries to parse source files…


No, that's a metadata hash it creates (probably from semver info, but also perhaps file contents) so that it can ask rustc to mangle symbol names.


No file contents, that would defeat incremental recompilation.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: