Thoughts on aggressive deprecation in libstd

Yes, because rustc cannot know the libraries' target version. However, the compiler could add a check whenever an API item is not found if it is available in an older version and suggest adding a corresponding target attribute to the crate; so it would only require minimal user intervention to remove the breakage.

It is certainly a good idea to

  • have cargo new insert the currently used rust version as target
  • have cargo package warn if no target version is specified and/or also insert the current version – I think a warning is in order, because whoever does the packaging may or may not be the original author.

That "minimal intervention" translates to several hours of hacking when the library in question is in the middle of a complex dependency graph. Good thing if you know that Cargo has overrides, but checking out the right crate and figuring the right syntax to compile it from a local path and making it work with the rest of the dependency graph is a considerable hurdle, not to mention that it's disruptive to any scripted deployment process.

For the end user who expects the build to just work that's outright impossible to fix.

Imagine the familiar "./configure && make install clean" path suddenly telling users of a program to go fix a minor deprecation in one of the libraries used, simply because your operating system uses a more recent version of the compiler.

1 Like

No. First, this only applies to libs whose authors have forgotten (even if it is there by default on cargo new) the target attribute.

Second, it only applies to libs that have not already been compiled.

Third, the compiler will output the specific crate, including location that is at fault and where to enter the exact statements to make the problem go away.

Lastly, we could make it so that the compiler warns, but not stops, if a lib does not specify the target and uses APIs that have been deprecated in the meantime, making the above suggestions.

Every Rust crate we use has to compile all the dependencies. That is, telling a user to install your Rust program is equal to telling him to compile all the dependencies. It's not Java, compilation doesn't happen on the developer's machine, it happens on the end user machine with the end user's compiler.

Third, the compiler will output the specific crate, including location that is at fault and where to enter the exact statements to make the problem go away.

To the end user that's utter gibberish. To the developer it's hours of hacking together a custom Cargo configuration just to temporarily fix other people's code. Out of the blue my Dockerfile will go to hell and I'll have to jump through a dozen hoops to fix it just because somebody deprecated a stable API somewhere.

Every Rust crate we use has to compile all the dependencies.

That is currently the case, but there is no particular reason why there shouldn't be pre-compiled crates.

In any event, we should make it so that rustc never really makes APIs unreachable unless a target version was specified that effectively removes the API.

Then we'd have a deprecation syntax like #[deprecated(soft=1.1, hard=1.2, remove=2.0)], where soft deprecation means: "This will show as deprecated in the docs, but not emit a warning unless the user calls rustc with -W soft_deprecate (or -D), hard deprecation means something will generate a warning on all builds with an equal or higher target version, and removed means that it is no longer available on builds with an equal or higher target version.

This will mean breakage only occurs, when a library developer actively screwed up (Edit: Or a feature was shown to be security-incident-inducing).

Btw. we also may want to add the deprecated atrtibute to syntax highlighters so that deprecated code is greyed out or something.

2 Likes

I’m definitely in favor of the #![rust = "1.1.0"] idea. If you specify the version of Rust that you are targeting, then the compiler should only warn you about deprecations that were in place by the time that version of Rust came out, and it should also warn you if you use anything that was only stabilized in a version newer than your targeted version.

I’m not in favor of #![allow(deprecated(foo, bar))], because if you’ve upgraded some deprecated feature, you’ve already bumped your Rust version, and you might as well fix all the other deprecations up to that version. You gain absolutely nothing by only fixing some deprecations.

Having a Rust version field in Cargo.toml should also help significantly. That way when you cargo update, Cargo can make sure that it doesn’t update any of your dependencies to versions that require a newer version of Rust than you are targeting. Also, when Cargo builds your code it should specify the targeted version to Rust, which will use that as the version for warnings if you didn’t specify #![rust = "1.1.0"], and if you did specify that attribute, then Rust should emit a warning if it doesn’t match the version specified by Cargo.

4 Likes

That is a very important point that I failed to mention. Cargo can hold library versions if newer ones require newer language features. Just one small nitpick: Cargo should fix the library target version to the version of Rust currently available on the system, not the one the library targets. Combined with backwards-compatibility via target flag, this would be enough to ensure non-breakage in most cases while also keeping availability high.

Do we need an RFC for this stuff?

Just one small nitpick: Cargo should fix the library target version to the version of Rust currently available on the system, not the one the library targets.

No, it shouldn't - if you did that you'd get deprecation warnings for the library.

I do think that cargo should warn loudly if a target version isn't specified, and crates.io should prevent crates being published which don't specify a target version.

I don't have much experience in Java, but I wouldn't call their strategy "aggressive deprecation". I don't know of a single time they've deprecated something because of the name. Looking it up, I've even found a few instances where official deprecation was actively not pursued.

I think the strategy of mixed quiet and loud deprecations are good, and in that case would be all for aggressively deprecating.

One other thing that does need to be asked is what the hypothetical Rust 2.0 is meant to be. It seems a lot of people want Rust's first opportunity at compatibility-breaking to be a great spring-cleaning. I don't think that's a good idea. Personally, I'd like 2.0 to be mostly removal of long-deprecated items and a move to making many more deprecations into warnings.

I know the sentiment's against me here, but consider how few languages deprecate in the way that seems to be suggested. Consider that the competition is primarily C++, which only just managed to get rid of trigraphs. Change is difficult, and it's never free when it happens.

For this matter, I think building things outside of std the right solution. If you want to be able to change something quickly, just don't put it in std. Having a blessed central repository and good, versioned, dependency management makes this far more tenable.

+1 for some form of version tagging, FWIW.

It is just as important for applications as it is for libaries. I want to ship my Rust app on Debian and CentOS. That means supporting older Rust versions.

Could you elaborate? If all dependencies are linked statically, what prevents you from shipping the app?

Is it intended that Rust ever be used in the large corporations?

In my experience, the larger the corporation the slower it adopts new stuff. My own company is only just finishing its migration to gcc 4.3.2 (released August 27, 2008), just because migrating is costly:

  1. A version of the compiler has to be selected
  2. A number of key applications are compiled and tested
  3. New bugs are found, it has to be identified whether they are caused by a code issue or a compiler issue
  4. In case of compiler issue, either a work-around has to be identified (disabling an optimization pass, for example) or a fixed version has to be used; in the latter case, back to (2)

The version of gcc 4.3.2 we use is not even vanilla, and on top a number of flags are used to work around known issues. Just validating the compiler itself and finding the set of flags/fixes was (from what I know) a 6 months project involving up to a dozen people at a time.

Once the new compiler is selected, then the migration starts. Starting from the base, every library is delivered and tested in two versions (old and new), so that the applications (users) can migrate at their own pace. Note that unless there is a specific issue with the compiler (slow library load, for example) it is often seen as a cost by applications which are already under pressure to deliver features which bring a measurable amount of money.

Finally, after a couple years, every application has migrated, and the cycle can begin anew.


Now, I am pretty much convinced that my company is pretty bad on the churn time here right now; we’re working on it though so it should get better in the future. On the other hand, I doubt we are the worst; I’ve heard horror stories from embedded developers stuck with proprietary backends that only work with older compiler versions.

The point is, expecting that everyone can be at worst ~4 cycles behind seems way too optimistic. In my experience, you should expect to count in years.

Now, maybe it’s just not Rust ambition to cater to this particular market share, or maybe there’s hope that with the frequent release of cargo packages the industry will naturally follow…

… however, in the case of my company, we are more interested in serving our customers with a >= 99.99% SLA than we are in renaming size_hint to len_hint. And while 4 9s is not that much, it’s hard enough to get to with a stable foundation.


In the same vein, I could see a keen interest in packages being tagged with the minimal rustc version that they can compile with, so that one may know which package are available.

4 Likes

I mean in the repos.

if rust aims to compete with c++ then it should not expect a 2.0 to ever occur

1 Like

I don't have much experience in Java, but I wouldn't call their strategy "aggressive deprecation". I don't know of a single time they've deprecated something because of the name.

Enumeration was superseded by Iterator. Apart from that, they truly have kept everything in place, because backwards-compatibility was their highest priority (which hasn't kept them from deprecating large swathes of their standard library).

Python also has a useful model of deprecation: They first announce it – usually before it occurs – then start a grace period where using the API will trigger warnings, then finally they remove it (however, they don't have target version switches, which is why many distributions keep multiple python versions lying around).

I think there is a broad consensus to warn where useful, lead devs to declare the target version (and provide sensible defaults), only break a feature when absolutely necessary, use version information to drive deprecation.

I don't think this is worth worrying about. If, say, in the nearish future (a) cargo new starts inserting the attribute by default, (b) crates.io starts rejecting uploads that don't contain it, and (c) cargo build starts giving a warning (but not a hard error) on crates that don't contain it, Rust is young enough that crates without it will pretty quickly become a non-issue.

1 Like

C++ does have breaking changes!

As an anecdote:

[quote] Straw poll, LWG Motion 12, Move we apply to the C++ Working Paper the Proposed Wording from N4190, Removing auto_ptr, random_shuffle(), And Old <functional> Stuff.

The removal of features was applauded.

LWG Motion 12 straw poll results were:

In favor: 48 Opposed: 1 Abstain: 13

Sutter declared consensus, motion approved.[/quote] (from WG21 2014-11 Urbana Minutes (revision 1))

Besides that, the C++ (both the standard and implementations) does "breaking bug-fixes" with minimal practical impact all the time, so it's not a gold standard of absolute backward compatibility.

1 Like

I have written up an RFC to capture the consensus of this discussion.

1 Like

Aggressive deprecation on major version releases are a good way to keep progressing towards a point where the API won’t need too much deprecation (if any). Now is a great time to move fast towards that point of API stability.

Basically the longer it takes to deprecate stuff, the greater number of code will exist that will be affected by deprecation changes.