I’ve been thinking recently about the policy of deprecating stable APIs in the standard library recently, and it looks like we’re about to accept an RFC which is paving the way forward for future decisions. As such, I just want to make sure that we’re all aware of what the repercussions may be! If you’re aware of the discussion on the size_hint
=> len_hint
RFC, you can skip the next background section
Background
We currently have an RFC to rename the size_hint
method on the Iterator
trait to len_hint
to make it more consistent with the term len
used for other collections. In a vacuum this RFC appears to have fairly broad consensus that it is the right decision to make, but we’re in a post-1.0 world where we need deprecation paths forward and can’t simply just remove size_hint
.
The proposed solution is to introduce len_hint
as #[stable]
while simultaneously deprecating size_hint
. The deprecations will not be removed until Rust 2.0 and hence all existing code will continue to work other than just receiving compilation warnings.
Why deprecate APIs?
The standard library at 1.0 has a considerable size, and it inevitably has a few minor inconsistencies in naming and other little aspects here and there (as seen with this recent RFC). As a result, we can either aggressively try to fix these inconsistencies, or we can live with them over time.
I personally would like to be able to fix all of these inconsistencies as they come up over time as it has a number of benefits:
- All future newbies will have a better experience with a more consistent language
- All existing Rust programs will continue to work, they’ll just get some form of warning about the newer form (which can be silenced)
- This sets a strong precedent for “the standard library is not stuck”, which I think is a huge boon for the development of the library over time. I don’t want to be wed to old APIs when small backwards-compatible changes can be made to slowly improve the experience over time.
There are, of course, a number of downsides to deprecating APIs
- A deprecation can be seen as a breaking change for one of two reasons:
- Migrating away from a deprecated API means giving up support for older versions of Rust as the new version likely doesn’t have the deprecated-for replacement.
- If one aggressively does not want warnings in a build. Deprecating an API sends a strong message that callers should change, which can sometimes provide unnecessary churn in the community (e.g. downstream crates also decide to change their APIs).
- If the standard library evolves radically over time, old code snippets seen, for example, on Stack Overflow will get out of date and may be entirely obsolete at some point in the future (riddled with deprecation warnings)
- Most deprecations are for minor features, such as the naming of an API, and it’s tough to see how it’s worth it for the costs above.
I would, however, like to proposed that the standard library should aggressively deprecate APIs and continue to evolve. This puts it in a great position to keep up with idiomatic Rust while maintaining backwards compatibility. I think we can take at least one measure to address the first con as well as the last con, but the second one may be fundamentally insurmountable.
Supporting old Rust versions
A major problem here is that updated code will no longer be able to compile against older versions of Rust, which can sometimes be quite important. It’s unclear to me whether this will actually be a problem in practice as everyone may stay up-to-date as much as possible anyway, but I think it’s at least worth considering.
The current story here today would be to slap #![allow(deprecated)]
at the top of a program and call it a day (after avoiding all new APIs), but this is not an ideal situation as there may be deprecations you want to know about and can replace with crates.io crates, for example. In the “ideal world” I would like to see the ability to do something like:
#![allow(deprecated(foo, bar))]
Here, only deprecation warnings for the features foo
and bar
are allowed, otherwise all deprecation warnings are printed. We would ensure that each deprecated API has an appropriate feature name (e.g. not rust1
) so it could be listed in the deprecation warning.
I believe a system like this (whitelisting deprecation warnings based on features) will easily allow crates to maintain backwards compatibility with older Rust versions. The list inside the deprecated
attribute I don’t think will grow without bound because over time if it’s too large you’ll just drop support for older Rust versions (which are likely super old by that point).
Immediate actions
In the near future, I would personally like to accept RFC 1034 assuming that a system (not the exact one, but similar to) like described above is implemented. I don’t think the RFC needs to be blocked on fleshing the system out entirely, but I do think it’s important to “just accept” the RFC without considering the implications of deprecating standard APIs. We’re going to be setting a precedent if we accept the RFC, and just want to make sure we’ve got all our ducks in a row!
How do others feel about this course of action? Specifically:
- Do you agree that the standard library should aggressively deprecate APIs for consistency?
- Do you think the ability of supporting older Rust versions is important?
- Do you think that
#[allow(deprecated)]
is sufficient, or do you think a more fine-grained control will be required? - Can you forsee other problems with deprecating APIs in the standard library?