rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

RFC: `multiple_crate_versions`

Open LukeMathWalker opened this issue 3 years ago • 2 comments

Give maintainers a mechanism to declare that their library is unlikely to work (e.g. fail at runtime) if there are multiple versions of it in the dependency tree. When the constraint is violated, a compiler warning is emitted to inform users of the issues they are likely to encounter.

Rendered version Pre-RFC discussion on internals rust-lang/cargo#5920

LukeMathWalker avatar Apr 02 '22 12:04 LukeMathWalker

This just seems like your standard problem with global state not playing well with multiple versions.

While I do agree that there are cases that just can't avoid global state, I think it might be worthwhile to consider an existing way this is solved already: the #[global_allocator] attribute. This is a form of global state that applies to a compiled artifact, and it complains if there are multiple definitions across the crate tree, including via multiple versions of a crate that provides one.

I think taking inspiration from an attribute like this would be a much better solution to the problem -- rather than annotating the entire crate as "breaking on multiple versions," annotate a specific item which contains global state and prevent multiple occurrences of that in the crate tree. That way, there's no need to worry about the mess that could be introduced by having that global state enabled behind a feature: if the annotation is there, the global state is there, otherwise it's not.

I'm not 100% sure how we'd want to specifically make this work, but here's my extremely unpolished syntax:

// in mycrate::mymod

// this makes mycrate::mymod::GlobalState now an identifier for a global state container;
// only one instance can exist in the tree,
// and that instance must satisfy *all* definitions of this trait in the tree if multiple versions are present
static trait GlobalState = Something + SomethingElse;

// somewhere else
impl mycrate::mod::GlobalState for MyType {}

Note that I do not propose this specific way of doing it, rather, I think we should explore ways of annotating specific items in crates as conflicting across versions, rather than the entire crate itself. Since I definitely can imagine a scenario where, for example, a crate like tokio might have to sequester the global state into its own sub-crate that is marked as multi-version-incompatible, rather than just marking one item in that crate.

Note that this also solves the need for weird version ranges in the lint: by explicitly specifying what the global state needs to look like, it's entirely possible for a crate to implement a version that satisfies the criteria of multiple versions explicitly, whereas a default implementation might only cover the definition for one version.


No recent replies but something I figured I'd add to this: basically, I am not against the idea of making multiple versions of a crate conflict, but I am definitely against manually specifying version ranges for conflicts since the logic can get really complicated really quickly, and it also leads to a lot of weird and confusing cases for users. I also really do like the idea proposed here and would like to see it explored fully before going with a crate-incompatibily lint as proposed here.

clarfonthey avatar Apr 04 '22 22:04 clarfonthey

Hi @clarfonthey! Thanks for taking the time to review the RFC 😁

While I do agree that there are cases that just can't avoid global state, I think it might be worthwhile to consider an existing way this is solved already: the #[global_allocator] attribute. This is a form of global state that applies to a compiled artifact, and it complains if there are multiple definitions across the crate tree, including via multiple versions of a crate that provides one. I think taking inspiration from an attribute like this would be a much better solution to the problem -- rather than annotating the entire crate as "breaking on multiple versions," annotate a specific item which contains global state and prevent multiple occurrences of that in the crate tree.

I do agree that it would be desirable, over a longer time horizon, to have a language-level solution to the problem: find a way to fold this implicit dependencies back into the type system. This could be an attribute similar to #[global_allocator] or a more general effect-like system (e.g. context and capabilities by @tmandry).
The design space for such a general-purpose solution is much wider: it must take into account the interaction with existing (and planned) language features.

This proposal is an attempt to find a solution that:

  • Has minimal implementation complexity;
  • Has minimal impact on the language surface;
  • Significantly mitigates the widespread issues in the ecosystem (80-20 principle);
  • Can be sunsetted with minimal impact if the language evolves to remove this problem class.

In the spirit of the above, I do agree with you on the following:

No recent replies but something I figured I'd add to this: basically, I am not against the idea of making multiple versions of a crate conflict, but I am definitely against manually specifying version ranges for conflicts since the logic can get really complicated really quickly, and it also leads to a lot of weird and confusing cases for users.

There is no reason to try to stretch this coarse solution into something more fine-grained: better to defer to a proper language-level mechanism, as you suggest.

Do you see the value of shipping a solution such as this lint as an intermediate step or do you believe that it'd be counter-productive/it doesn't pull its weight?
From what I can see, I do not have much confidence that we'll see a language-level solution land in a near/medium timeframe (<2 years). All the proposals I see moving around to handle "implicits" or effects are still in the preliminary stages and have yet to build consensus; we'd then have to agree on a technical design, scope the work and actually make it happen - it's likely to require multiple years.
At the same time, the bulk of the occurrences of the problem described in this RFC could be solved by the lint proposed here, improving the experience of consumers and allowing crate authors to evolve their APIs without fear of causing widespread disruption in the ecosystem (see this tweet from @hawkw as an example) on a much shorter time horizon.

LukeMathWalker avatar May 30 '22 06:05 LukeMathWalker