Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify Rust compatibility of nursery crates #1619

Closed

Conversation

alexcrichton
Copy link
Member

Currently there is unfortunately not a clear guideline for what the
compatibility of all rust-lang-nursery crates are. The purpose of this change is
to set forth such a guideline to ensure that the nursery crates are all
consistent. It may also likely be the case that many other crates in the
community wish to follow such a policy as well.

This change proposes that all nursery crates are compatible with the previous
two stable releases
of the compiler. For example, if 1.10 is the current
release then all crates are guaranteed to compile on 1.8 and 1.9. Nursery crates
may compile on older versions, but this is not a guarantee nor can it be relied
on.

Currently there is unfortunately not a clear guideline for what the
compatibility of all rust-lang-nursery crates are. The purpose of this change is
to set forth such a guideline to ensure that the nursery crates are all
consistent. It may also likely be the case that many other crates in the
community wish to follow such a policy as well.

This change proposes that all nursery crates are compatible with the **previous
two stable releases** of the compiler. For example, if 1.10 is the current
release then all crates are guaranteed to compile on 1.8 and 1.9. Nursery crates
may compile on older versions, but this is not a guarantee nor can it be relied
on.
@alexcrichton alexcrichton added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label May 17, 2016
@alexcrichton
Copy link
Member Author

cc @rust-lang/libs

@BurntSushi
Copy link
Member

BurntSushi commented May 17, 2016

Would it be helpful to clarify that the minimum Rust version can be increased via a semver compatible version bump? For example, a crate at version 0.1.1 might support Rust 1.3, but the same crate at version 0.1.2 might require Rust 1.4. For context: rust-lang/regex#206

cc @DanielKeep

@aturon
Copy link
Member

aturon commented May 17, 2016

@BurntSushi Yes, that's a good idea.

Also, note that this is a proposal for an initial policy, while Rust is still very rapidly evolving. As things slow down, we've talked about possibilities like LTS (long-term support) releases, which could provide a clear point of agreement across the ecosystem in terms of old versions of Rust to maintain compat with. Likewise, we'd also like to explore tools for cfg options based on feature availability, so that you can polyfill your way back onto older releases. But those ideas are for the future, and this policy proposal seems like a reasonable place to be for the time being.

@alexcrichton
Copy link
Member Author

Agreed, I added some text saying that bumping the version isn't a breaking change

@Ericson2314
Copy link
Contributor

Other than this means it's going to take a really long time to use associated constants in bitflags :), sounds good!

@huonw
Copy link
Member

huonw commented May 18, 2016

What about full rust-lang/non-nursery crates (e.g. libc)? Also, do we want to explicitly note in the RFC what @aturon said (i.e. this is explicitly intended to evolve as the language evolves)?

@nrc
Copy link
Member

nrc commented May 18, 2016

For future crates which are ready to ascend to the nursery, is this to be considered a prerequisite? Or will they have some time to adjust? Either approach seems ok to me, but probably good to make explicit.

@sfackler
Copy link
Member

I think it's fine if a crate enters the nursery running on a "too modern" rust, but it would then have to hold back on upgrades until the right number of new releases have landed.

@sfackler
Copy link
Member

I assume it's okay for a nursery crate to flag functionality that requires a new version of rust behind a feature?

Do we want to guarantee that it's possible to build a nursery crate 2 versions back or that it will by default? Can a crate have a dependency with a version range that includes releases that build on only new Rust (e.g. bitflags = ">= 0.5, < 0.8")?

@alexcrichton
Copy link
Member Author

@huonw I've added a note explicitly saying that we'll probably follow LTS if they ever exist, but also just noting that this policy may change. I would personally expect rust-lang to be held to the same standard, e.g. those crates must compile on at least 2 older releases for now.

Note that in practice the only example of this right now is the libc crate which compiles on 1.0+, so it's not really an issue yet :)

@nrc, @sfackler I'd personally consider this a gate to moving in the nursery. That is, if you don't compile on the current 2 older stable releases, you'll have to wait to get promoted or add in API changes to fix compatibility.

@sfackler yeah I added some wording saying that by default the current version must compile on the current release an 2 older ones, but I personally think it's fine to have off-by-default features which break that compatibility.

be dropped if it was before two stable releases ago (e.g. by updating the
continuous integration configuration), or the change must wait to land
otherwise. If the minimum vesion of Rust is increased then this is not
considered a breaking change (e.g. does not require a new major release).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

femtonit: ie rather than eg

@kamalmarhubi
Copy link
Contributor

Note that in practice the only example of this right now is the libc crate which compiles on 1.0+, so it's not really an issue yet :)

Should something be added to RFC 1291 to specify a policy for libc?

the current stable release is 1.10, all nursery crates will compile successfully
on both 1.8 and 1.9. Some nursery crates may compile on older versions, but this
is not guaranteed and changes to the crate are allowed which bump the minimum
rustc version requirement.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewording suggestion:

The most recent version of a rust-lang-nursery crate is guaranteed to compile on the current release of Rust, as well as the two previous stable releases. For example, if the current stable release of Rust is 1.10, all nursery crates will compile successfully on both 1.8 and 1.9 in addition to 1.10. Nursery crates may compile on older releases of Rust, but this is not guaranteed and cannot be relied on. Changes to nursery crates that increase the minimum supported Rust release are explicitly allowed, and are considered semver compatible changes: they do not require a major version bump for the nursery crate.

@kamalmarhubi
Copy link
Contributor

Forgive the surface level suggestions, I just found the first paragraph a bit hard to follow on first read.

Are we only guaranteeing that the crates compile? Or should we also specify they pass their test suite on some set of platforms, say tier 1? Changes that don't meet that are probably pathological, but for completeness' sake.

@alexcrichton
Copy link
Member Author

Ah yeah it was implicit that crates run tests as well as compiling, and yes it was also somewhat implicit that this is intended for tier 1 platforms currently.

@alexcrichton
Copy link
Member Author

🔔 This RFC is now entering its week-long final comment period 🔔

The libs team is leaning towards merging this as-is (compatible two versions back), but we're always open to comments!

@alexcrichton alexcrichton added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed I-nominated labels Jul 26, 2016
@DanielKeep
Copy link

DanielKeep commented Jul 27, 2016

I'm fine with "we only promise to explicitly support the last two releases", but I just wanted to point out that there are people out there still distributing rustc as old as 1.6 (NixOS) and 1.7 (openSUSE and Ubuntu).

What I'm not fine with is breaking backward compatibility not requiring a semver bump. This means I can (for example) check out code that is known to compile with Rust 1.4, compile it with a Rust 1.4 compiler, and have the build fail. To me, this completely undermines the point of following semver in the first place.

If the crate ecosystem had metadata about what version of the language a crate needed to compile (something I proposed prior to 1.0, but which was deemed unnecessary), this wouldn't be an issue, but it doesn't and it is.

I am tired of having code just break because some transitive dependency three levels deep pushed a new minor version that causes the build to fail, and then I have to start bisecting transitive dependencies to try and figure out the exact version where everything caught fire (I don't update dependencies on a regular basis in part because of this issue), and then I have to hack the manifest to exclude the incompatible versions because I bothered to promise I'd support old versions of Rust, but oh wait, one of the dependencies in the middle has a broad version range and Cargo refuses to select the old one I want so now I have to roll that back as well and oh god it never ends please someone kill me!

Hell, I can't even just make a new release with a higher version requirement, because changes in dependencies can retroactively break old releases, which to my mind is utterly unacceptable.

I am terrified of cargo update; of accidentally running it in a library repo I'm working on and having to fix all the direct and transitive dependencies by hand. The attitude of "who cares about old code?" is a big part of why.

To be honest, if you aren't going to try and make sure old code continues to compile, I don't see the point in saying you follow semver. Either back compat matters, or it doesn't; you can't have it "kind of matter for a little while, but after 3 months you're on your own".

Apologies if this has offended anyone, but this is something I feel extremely strongly about.

@alexcrichton
Copy link
Member Author

@DanielKeep it seems untenable to me to say that any semver-compatible series of a nursery crate remains compatible with one version of Rust forever. This means that if the crates ever hit 1.0 they'll never be able to take advantage of new language features until the crates hit 2.0.

The idea in this RFC was to say that "two releases back" is sufficiently old enough for basically anyone at this point. If it turns out that there are major distributions lagging quite behind then we can perhaps change that number. It should also be pointed out that most nursery crates will end up supporting much older than two versions as they don't change that often.

It may also be worth separating out some of the concerns you're talking about. "Random changes to the dependency graph" should never happen in Cargo with a Cargo.lock, which is highly recommended for all applications. Libraries themselves are affected by semver upgrades, but that's an entirely different story as well.

@DanielKeep
Copy link

This means that if the crates ever hit 1.0 they'll never be able to take advantage of new language features until the crates hit 2.0.

This is not really true; scan-rules is an example of using version detection to progressively add support for new features without breaking backward compatibility.

As a secondary riposte: so what if you have to bump semver? They're just numbers; it's not like we're in any real danger of running out of versions.

It may also be worth separating out some of the concerns you're talking about. [...]

Well, I'm not sure they are separable, but I'll try. First of all, I care about backward compatibility. If it's reasonable to support old versions of Rust, I'll do so. I want to support all reasonably "in the wild" versions at a minimum. I want someone who has (picked at random) 1.7 on their machine to look at my crates and programs, see that it's supported, and be confident that they can compile and use said crates and programs.

Problems that this policy (specifically in regards to not bumping semver when breaking support with old versions of the language) may and/or will cause:

  • Neither I nor my users can safely use cargo update. There's no way of knowing if doing so will break the build or not. If it does break the build, fixing the issue means manually bisecting the version of the failing dependency until it works. In one case, I actually had to roll back the parent dependency of the one that failed, then re-start the process all over again.
  • For libraries, I have to exclude everyone from updates. Once a breaking version is published and identified, I can at least record the compatible version in Cargo.lock. I cannot do this for libraries. At that point, my only recourse as of now is to forcibly remove compatibility with the bad version in the library's manifest. Because I can't have dependencies conditional on Rust version, this means I have to exclude the update even if it would be compatible with the compiler the user has. This sucks.
  • Cargo.lock isn't sufficient. Let's say I'm a new Rust user. I install Rust from my package manager, and it's a somewhat old version (long-term service branches are a thing). Actually, I don't even need to be stuck on an older version of Rust, I just have to be starting a new project targeting an older version of Rust. I cargo new --bin thing and add a dependency. I check the documentation, and it says the crate is supported for my version of Rust. Great! Then I compile, and everything explodes because it turns out a transitive dependency broke compatibility without bumping semver.
  • The above also applies to testing crates. Checking in Cargo.lock isn't a good solution because that means I'm testing a set of dependencies different to what my users will get if they start a new project, or even just fork existing code. To reiterate: this is an example of code that worked and hasn't been touched, being compiled with the appropriate toolchain breaking on semver "compatible" updates.

The "proper" solution to this would be to teach Cargo about language versions; in that case, this whole issue just vanishes because Cargo wouldn't select incompatible versions in the first place. But it doesn't; the only two ways these problems can be fixed is to either have crate maintainers break semver when they break compatibility, or having downstream users manually hack version ranges into their manifests, doing the legwork themselves.

At the end of the day, what I want is for working code I wrote to continue working both for myself and for others without having to actively fight the package ecosystem and tooling.

@sfackler
Copy link
Member

Annotating crates with their supported Rust versions seems like a good idea regardless of this RFC, but I would note that Cargo's dependency graph resolution logic is already fairly naive, and adding more constraints may worsen things there even more until someone gets around to integrating a SAT solver so we can do it right.

@Ericson2314
Copy link
Contributor

#1133 effectively adds language deps as long as std evolves in lockstep with the language.

@alexcrichton
Copy link
Member Author

Using compiler detection is just another way of introducing silent dependencies if you don't even have to opt-in to the "new features enabled by a new compiler". All existing libraries still may have to bend over backwards to provide a backwards-compatible interface and it may not even always be possible (e.g. language features like specialization).

Also semver is not just numbers, there is a very real cost to bumping the major version. Each major version bump introduces a split in the ecosystem for who's on what version. This was very readily apparently when libc when from 0.1 to 0.2, a dependency which is shared among many crates. The more crates a type is reexported in as a public API, the harder it is to bump the major version of that type. The libc crate is an extreme case where it's in nearly every public API, but it suffices to say that bumping semver versions is not at all free.

This is then compounded with the fact that nursery crates are intended to indeed be the reexported types in public APIs quite broadly. Crates like log are ubiquitous throughout the ecosystem and wouldn't work if everyone was on a different version.


I think that there's still a lot of conflation of ideas in what you're talking about as well:

  • With Cargo.lock the dependency graph never changes, full stop.
  • Without a Cargo.lock then yes, the dependency graph can change if cargo build is run on two different machines.
  • With a cargo update, yes the entire graph can change, which is why this is never recommended.
  • With cargo update -p foo, yes, more of the graph than just foo can change, but it's intended to be minimal. Yes, though, it can pull in new versions which may no longer compile on older versions of Rust.

It seems fine that we'd want Cargo to learn about language versions, especially if it culls candidates from resolution based on this. That seems like a separate feature though, and one that doesn't block this RFC. It can be an independent policy that the nursery crates all provide a bare minimum of 2 versions back of compatibility, and perhaps more. If we gain the ability to write down in Cargo.toml what the minimum version is, then we can start doing so.

@sfackler
Copy link
Member

sfackler commented Aug 1, 2016

@alexcrichton I don't think an automatic detection system is required, or even desired. A simple rust = "1.10" field in the library metadata seems like it would do fine, and projects can use CI to enforce that minimum version.

This is getting off on a bit of a tangent though.

@brson
Copy link
Contributor

brson commented Aug 1, 2016

I share the concern about minor point releases being able to break old versions of rust. It is a semver violation in my opinion. I think if we actually find ourselves in a situation where we are going to break an old version of Rust by doing a minor version bump then we should stop and think hard about the tradeoffs. This policy shouldn't be a blanket mandate to break things, but I also think that some policy is better than the status quo.

@BurntSushi
Copy link
Member

I do share @brson's and @DanielKeep's concerns about permitting a new required minimum version of Rust in a semver compatible version bump. I think there are a couple key trade offs and issues at play (that @alexcrichton mentioned):

  • Many of the nursery/rust-lang crates are critical parts of the ecosystem not only for functionality, but for API sharing. For example, many crates incorporate types and traits from the log, libc and rand crates in their public API. In our current ecosystem, this makes a major semver bump incredibly painful.
  • One of the benefits of the train model is that everyone can stay up to date with the latest and greatest Rust features. This strongly motivates the use of new Rust features in crates, especially in ones that are widely used.

The above two points seem to be in direct competition with each other. With that said, I do think it's within our discretion to define this policy. Additionally, I do feel like the current state of the community is that most folks are updating to new Rust releases, which I think suggests the policy in this RFC strikes a decent enough balance. The RFC does mention that this policy should be reconsidered in the future, particularly if more parts of the community stay on older versions of Rust.

I am of course in favor of the less controversial points, which is that we have a policy saying something about the minimum Rust version supported by our official crates.

@alexcrichton Could you maybe talk a bit more about the viability of specifying a minimum Rust version in the Cargo.toml? @DanielKeep suggested this, but it's not clear to me whether we want it or not. I'm not sure it's necessarily on topic for this RFC, but if we did have it, it might make the semver policy stated in this RFC more palatable.

@steveklabnik
Copy link
Member

Personally, the way I see this policy is that it's good for now, and can be revised later. Right now, there's no guarantee; so making it two releases back is an improvement. And ideally, someday, we end up with LTS Rust releases, and the policy can be changed to coincide with that.

@BurntSushi
Copy link
Member

@steveklabnik To be clear, I think guaranteeing two releases back is the uncontroversial part of this. :-) The specific bit that is causing trouble is requiring a new minimum Rust version for a crate wouldn't require a semver major version bump (as the RFC is currently written).

@steveklabnik
Copy link
Member

@BurntSushi yeah sorry, I guess I wasn't as clear; it's the same thing for both. Right now, there's zero policy, I'd prefer this to getting dragged down over a relatively small point.

This kind of thing has always been a tricky point; I've seen it in other ecosystems as well. Each project kind of picks whatever they want with regards to semver; I've done both options before. One of the problems with "dropping a language version is a major bump" is when you don't intend to ever release a new major version, yet the old language versions have been EOL'd. I have one project that's still on Ruby 1.8.7 for this reason... We're facing a similar problem here. That's one reason why I mentioned the LTS bit.

@carllerche
Copy link
Member

I agree strongly w/ @DanielKeep and @brson. Not having a semver bump when a rustc version is deprecated will result in breaking down stream code.

I personally take backwards compatibility very seriously, In Mio, I explicitly run CI using the oldest supported version of rust (https://github.com/carllerche/mio/blob/master/.travis.yml#L5-L7). It has happened in the past that my build as broken due to a downstream library silently deprecating the version of Rust that Mio supports.

If silently deprecating rustc versions becomes the norm, what will happen is that libraries that take backwards compatibility seriously will start having to special case these versions in their dependencies. Aka, having to depend on "libc <= 0.2.13". This seems like a far worse outcome. Making a semver bump should be cheap.

I also respectfully disagree w/ @steveklabnik's opinion that something is better than nothing. The greater rust community looks to the rust team for leadership, and policies like these have a far greater impact than just how the rust nursery is managed. Once a policy is set, it will become spread out into the community as a model and gain momentum. It will be significantly harder to amend the RFC than set it initially.

@Ericson2314
Copy link
Contributor

Ericson2314 commented Aug 9, 2016

I'm writing an RFC (in some ways a subset of #1133) to address this issue. edit also there's #1707

@steveklabnik
Copy link
Member

Apparently @othiym23 has some strong feelings about how this works in node: https://twitter.com/othiym23/status/762679697914245121

@Ericson2314
Copy link
Contributor

Ok wrote #1709 with motivation from this thread.

@alexcrichton
Copy link
Member Author

The libs team discussed this RFC during triage yesterday and the decision was to close. The crux of this RFC is that updating the minimum required version of Rust is not a semver-breaking change, which seems to be the most contentious point. This in turn is tied closely enough to other features like LTS releases or Cargo understanding the minimum language version for crates. As a result the policy would likely change here as soon as those are implemented, so our thinking is we can just wait to reformulate this policy once one of those is implemented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.