rfcs
rfcs copied to clipboard
Remove the restriction on the `links` attribute in Cargo.toml
Let's say for example that:
- Your project depends on both libraries A and B.
- Library A depends on library
foo
0.1, and library B depends on libraryfoo
0.2 -
foo
0.1 depends onfoo-sys
0.1, andfoo
0.2 depends onfoo-sys
0.2 -
foo-sys
has alinks
attribute in its Cargo.toml.
This raises an error from Cargo telling you that only one library can simultaneously link to the same native library.
More generally, whenever a library that has a links
attribute bumps its version in an incompatible way, and libraries start to use the new version, builds can break. This is notably what happened to the winapi
crate a few weeks ago and that broke half of the ecosystem. I'm personally opening this issue following a similar problem with openssl-sys
(foo
being openssl
, A
being postgres-rs
and B
being tiny_http
(but B
could also be hyper
)).
In addition to this, this means that if you randomly pick two crates from crates.io that work separately, you can't even be sure that you can use them at the same time.
A recent Cargo pull request changed the error message to suggest people to update or pin their dependencies. Forcing people to always use the latest version of a crate promotes the inverse of stability. Once you use a specific version of a library, you shouldn't be forced to update it just because another of your dependencies is incompatible with it. It also means that all the reverse dependencies of all -sys
crates must be immediately updated by their maintainers.
I see two solutions:
- Forcing crates with a
links
attribute to never ever bump their major version. This is obviously not practical. - Removing the restriction of the
links
attribute, so that multiple crates can link to the same native library. This is the less bad of the two.
cc @alexcrichton
What happens when two crates with the same links
specify different linkage information?
I can see three possible behaviors:
-
Force the linkage information to always be the same (it would be less restrictive than today but still wouldn't fix all issues because a library could legitimately modify the way it links to a library).
-
Merge the two linkage infos.
-
Merge the two linkage infos and print a warning if they are different.
When thinking about this it's important to keep in mind why this restriction exists in the first place. Native C libraries don't have symbol mangling like Rust does so a library foo 1.0 and 2.0 are likely to have the same symbol names but perhaps have ABI differences and such. This means that if you attempt to link two versions of a native library you're basically almost guaranteed to have linker errors or weird runtime crashes. As a result, links
is attempting to give a more human readable error to prevent these sorts of situations.
As an example of this, the libgit2 library that Cargo uses is changing its ABI all the time. These are picked up by libgit2-sys
over time and the underlying C library changes versions as new releases of libgit2-sys
are done. This means that it would basically be an error 100% of the time to include multiple libgit2-sys
versions in one compilation (hence the error being desired here).
In the case of winapi
and openssl-sys
, however, it's possible that links
is a little too eager in presenting an error. Neither of these crates will actually cause a problem if multiple versions are linked because the underlying native library is the same among major versions of the *-sys
Rust bindings. Basically what happens is that you'll link the same underlying dynamic library twice, so nothing bad happens.
So given that background, I think that removing the restrictions are plausible, but we'd be paying the price with weirder linkage error messages as well as possible runtime crashes. On the other hand we could relax the restrictions a bit by waiting to see what the build scripts say is being linked. If the same library is linked twice, but not dynamically, that's in theory the only source of an error. This means that you could have multiple versions of one links
library so long as they're all linked dynamically (as winapi
and openssl-sys
are).
In general it's not possible to merge two build script linkage outputs because the ABI could be different which would affect the correctness of the underlying crate bindings.
A pr where I previously got bit by this: https://github.com/rust-lang/cargo/issues/914 (warning: really old).
I'd like to re-iterate that it might make sense to emit a warning here, but it is a trade off:
- current behavior (error) allows a subset of legitimate setups
- warning instead would allow users to construct (additional) invalid setups, but also would avoid forbidding a class of legitimate ones.
In a perfect world, we'd have some countermeasures against users doing bad things (using info in the libraries to ensure our function signatures in rust match those provided by the non-rust lib, only have enough info for this if lto &/or debug info is included) but that's a lot of work for something that isn't always available.
As a slightly crazy idea, the general issue with static libraries not having namespaced symbols could be solved by having rustc apply symbol mangling to static libraries' symbols when rustc links static libs into the final binary/library (the complication would be that rustc would need to be told which static libraries we're linking depend on other static libs, so it could appropriately mangle everything, and seeing what the impact would be on libs built with rustc that expect to export symbols included via a static library).
I'm not convinced this is really a "static vs dynamic" issue (one can construct static libraries as a single unit like a dynamic lib, it just isn't commonly done because it's inefficient in many cases).
Note that:
If the same library is linked twice, but not dynamically, that's in theory the only source of an error.
Depends heavily on how "library" is defined and how we determine 2 libraries are unique. Using names is insufficient. I believe the foolproof test would need to be whether the 2 libraries contain any symbol names that collide.
I just encountered this while trying to do a quick and nasty debug on a gstreamer plugin in a Rust app. Having a way to temporarily bypass this (e.g. CARGO_REALLY_BAD_IDEA_ALLOW_LINKING_ANY_NATIVE_LIBS_WITHOUT_ERROR=1
) would help immensely.