Thoughts on aggressive deprecation in libstd

Yes!

No. I think renaming is a weak reason. Has it ever shown up as a point of confusion for newcomers to the language? And has it ever led to confusion that produced incorrect or even dangerous code? I estimate both are negative.

I think the gains in this particular case are not clear, and I don’t think that renaming something that doesn’t touch on the critical issues just mentioned, it doesn’t deserve the effort it takes to cope with deprecations from both users & developers.

2 Likes

I agree with Gankro, the bar for deprecations should increase with time. Deprecations for really good technical reasons (say, because it’s really wrong, or superseded by a much better alternative) should always be pursued aggressively, but small inconsistencies in naming… meh. Everyone deals with sub-par names all the time. After two years, the churn may not be worth it. But maybe this should be relative to when the specific feature being deprecated was introduced? Renaming a thing that’s been around for ten versions is frivolous, but introducing it in 1.x and renaming it in 1.(x+1) causes less pain. Still looks unprofessional, but so does inconsistent naming, and RFC process + code review + trains should catch most of these before they get into stable.

Regarding lints: I like the sound of features for allow(deprecated), but the main advantage is that you can hold onto specific deprecated pieces without letting the rest of your code bitrot, and that can mostly be achieved with #[allow(deprecated)] on the right items. Maybe if #[allow(deprecated)] use std::foo:Foo; disabled all warnings for Foo in this module?

4 Likes

I’m broadly in favour of aggressive deprecation.

One thing we should be aware of is that Rust has had a reputation (due to being developed in the open) as a rapidly breaking language. The big fanfare over 1.0 has remedied that to some extent, however, if we are seen as a language with lots of deprecation, then we might recover that reputation, which would be a shame.

I think this should not mean we don’t do it, but that we should keep in mind how we market these things.

4 Likes

Yes.

Yes, though not a blocker.

Fine-grained control is always nice. Without it, it’s just so binary: as soon as I decide one deprecation is okay, I miss out on all the other ones I may care about.

One question is what to do about docs. If we start to get too crufty, there’s an thought that maybe we should hide deprecated items, but then it’s hard to discover what old code does.

1 Like

The same is true for using “new” APIs. Code that uses socket timeouts will not compile on Rust < 1.2, for instance.

An idea that I’ve kicked around before to prevent annoying deprecation warnings is to annotate crates with #![rust = "1.1.0"]. Since stability attributes already include the Rust release the API was introduced, deprecation warnings can be omitted if the deprecation version > rust attribute version.

10 Likes

Yeah I definitely agree that rustdoc will probably need some love over time. Right now I don't think there's a great story for hide-by-default APIs, but my thoughts here are to show all deprecated apis for N versions, and then once it's N versions old rustdoc just doesn't render it at all. I suspect N could be ~4 to allow deprecated APIs to stick around in docs for 4 months, and after that they just disappear in all but the code.

We could, of course, add more fanciful facilities for hiding APIs, however, in which case this'd just be a "hide by default" after N versions instead of "don't render at all".

I do think it's tough to make a blanket "this is worth it" or not statement here. For example if BTreeMap::get did not have the same name as HashMap::get, I would very much want to rename the APIs as it saves me a trip to the docs every time I use BTreeMap. Something like size_hint in this specific case, however, is somewhat more ambiguous as it's rarely used and it could just be "one of those things" which you memorize instead of following a pattern to get.

I think this is a super important point as well! Just because we're deprecating something does not mean it's free, there are still costs associated with it. For example deprecating the name std in favor of something else would be a little... drastic!

Right, but I think this is somewhat orthogonal. Taken to the extreme this means we'll never add a new feature to the standard library, which I don't think is infeasible. As a library author I would like to have my crates work as far back as possible, but if I use a new feature from Rust I don't mind at all requiring a newer Rust version to compile.

Yeah I've thought of this in the past as well, but the problem I've found here is that it's still a very binary distinction. For example, in the set of APIs deprecated in 1.x, some could be renamings, some could be for bad performance, and some could be in favor of a higher quality crates.io crate (possibly). In this case I only want to opt-out of the renamings warnings, but I still want to deal with bad performance and using higher quality implementations.

1 Like

If I want to be compatible with Rust 1.x.y because that is what Debian packages or my build/release automation uses at the moment, I don’t care what reason the deprecations have. There are good reasons to be compatible with older Rust releases even when developing with a newer compiler. The same reasoning applies to using newer versions as well. Conditional compilation keyed on Rust version could help with efficiency issues etc., but it’s not everything.

2 Likes

Everything’s a trade-off but I hope deprecating for minor name inconsistency will be a really rare thing. You don’t want a naming situation like PHP has, of course, but stuff like size_hint doesn’t seem like a big deal & in the future when Rust has been stable a while that change would probably be very annoying.

Deprecation makes a lot more sense when its, for example, a new function with a “better” type signature, or abstracting commonalities of structs into traits & then deprecating some methods on the struct. I think those kinds of changes should be pursued aggressively.

1 Like

I worry that aggressive deprecation will not be worth the cost. Keeping Rust code up to date should not be a full-time job.

I’d be wary about changing names, except those that are obviously wrong. You should be able to support older versions of Rust without having spurious warnings about size_hint vs len_hint. It feels a bit like class and typename in C++ templates: it’ll just lead to inconsistency.

Broken or dangerous things should obviously be deprecated, but replacement APIs that are merely better need not force everyone using the old API to change their code.

This is pretty well known territory. I think the best approach is where the whole library is versioned (1.0, 1.1, 1.2), and application authors can say --target-libstd=1.0 and receive all deprecation warnings for < 1.0, but not APIs deprecated in 1.1.

Another bonus is that you can error if the caller tries to use an API that’s only available in 1.1. This will matter significantly more when Rust has a stable ABI.

7 Likes

I have no opinion on whether or not size_hint should be deprecated, but I generally do support an aggressive deprecation strategy.

However, I think it’s important that we seek to put a cap on the maximum number of deprecations that can happen in any one release (relative to the prior release). Giant walls of warnings only lead to people ignoring warnings altogether. We tread a fine line here. Given that Rust will have nine minor releases over the next year, I would favor a policy of spreading out deprecations when feasible.

I also agree that the bar for deprecations should rise over time.

At the moment it’s fine to assume that people will be upgrading their Rust code regularly as new minor releases appear. Rust is still a young language and people still remember well the speed required to keep up during alpha. However, as Rust matures, we should begin to assume that people will be stuck on old versions and perhaps try to accommodate them via a version attribute as proposed above.

This entire discussion is also an argument for a rustfix tool that would automatically clean up renamed APIs, as gofix once did.

I think it’s okay to aggressively deprecate things for the first few releases to have things as polished as possible going forward. This includes even minor renamings like the one discussed. In the future, I would expect, as other’s have mentioned, that the height of the bar to deprecate a feature would be proportional to the amount of time the feature has been stable. However, I think we should always deprecate features that have clearly superior alternatives, regardless of the features age.

If we end up with some kind of --rust-version=X.Y[.Z] option as originally proposed in #1122 before it was pared back, I would expect to only receive warnings for features deprecated before the specified version. Without the ability to specify a target version, I think specifying which feature deprecations to ignore is probably the best approach.

Looking at what other projects have done in the paste might be prudent. For instance, the Qt libraries have a much larger API surface than the Rust standard library; Qt has also been around for far longer (since 1991!). What Qt does is deprecate the old API and then undocument it too. No loud warnings are emitted when deprecated APIs are used (even though they could have been; most C++ compilers provide #pragma, #error and #warning directives that can be used for custom errors and warnings); old code just continues to compile while new code uses the new methods.

The old methods stick around for the life of a major release, which for Qt 4.x was seven years. The docs for the deprecated methods could actually be found, they were just tucked away into other parts of the documentation, away from the “main” docs for a given class.

To sum up:

  1. Aggressively deprecate old APIs. Code using them continues to work until the next major release.
  2. Do not by default emit warnings for the use of such deprecated APIs. Don’t create problems for people maintaining old code. It’s just the right thing to do; when those APIs were used, they were stable and recommended. The contract provided was “this will work at least until the next major release” without “…but we may hassle you about it in the future.”
  3. Make docs for deprecated APIs hard to find by default. Rust doc pages could have a settings checkbox for “show deprecated APIs” which is by default unchecked; the point is that it’s hard to use a deprecated API by mistake.
7 Likes

+1 let’s not live with debt/annoyances forever (even if they’re small).

Emphasis mine.

To expand on this, Python used to throw DeprecationWarnings when old APIs were used. This was turned off in Python 2.7 because everyone found it extremely annoying.

2 Likes

If we limit these warning to the root crate (not the dependencies) we will be just fine in my opinion. Most stuff will take < 5 mins to fix.

I’m just someone who saw this thread on reddit.

I like the idea of doing the deprecates by version number by crate/library. If my application needs to support stdlib >= 1.1 and libFoo >= 0.5, then I probably don’t want deprecation warnings for things that were depreciate in 1.2 and there’s no alternative in 1.1

By the same token, it should warn or outright error if I use stuff added after minimum version I want to support.

But there should probably be a “broken” flag that super deprecates something. Think of things like gets from the C stdlib.

This topic kind of brings up, at least in my mind, the topic of ABI.

I don’t recall seeing the topic mentioned in any documentation I’ve read so far, but it doesn’t look like Rust has a stable ABI. (Some googling finds me https://github.com/rust-lang/rfcs/issues/600)

Normally, at least in C world, you only want to break ABI compatibly with a major version release. I don’t know if adding a new method (in order to rename an old one) to a trait breaks ABI compatibly or not. But that’s something you’ll want to eventually document and then possibly worry about when changing the standard library, or any library really. (Or any application that has plugins.)

1 Like

I strongly agree, especially for consistency, so I would highly be in favor of what people are calling frivolous to changing size_hint to len_hint. As someone mentioned earlier, if you don’t change it early on, you’ll end up with something like the monstrosity of an API that PHP created. I would much rather not live with debt.

A small number of people may cry of these changes early on, but I am willing to bet that many more will pin them as points of a rant if they’re kept as is.

Between minor versions, yes, but not between major versions. Projects lock to particular compiler and library versions all the time.

A note about the docs: never hide anything on a documentation page. Use some css to gray it out, and add a note with the new function to use. That would be the most useful solution. Just please make is searchable with Ctrl+F. Apple’s documentation is one of the worst offenders of this, hiding everything by default, and it’s absolutely aggravating.

7 Likes

The word that concerns me the most in this proposal is “aggressively.” I understand that sometimes APIs are truly awful, like the infamous case of creat in Unix, or the security issues of gets. An occasional deprecation of a exceptionally awful API is, I think, understandable. And at this early date, even size_hint seems reasonable enough to fix. (Update: The proposed fix means that len_hint would return incorrect values when applied to Rust 1.0-era iterators. This feels a lot closer to a breaking change to me.)

But at the same time, I just spent several weekends of my life fixing libraries that were broken by the push to 1.0. I’m exhausted from Rust churn and breakage, and I’ve finally been enjoying the idea that I can write code, and I can expect it to stay written without constantly revisiting it. I have the time to actually learn Rust for real, and to figure out good ways to accomplish common tasks. And most of my third party dependencies actually build on any given day. I finally feel confident that I can start to use Rust for real software.

Here’s how I imagine “aggressive” deprecations playing out:

  1. Library X works perfectly well under Rust 1.5, and it is widely used by other libraries and by applications. Library X is mostly stable, but needs the occasional bug fix or minor enhancement.
  2. Rust 1.6 deprecates a bunch of features used by library X, leading to massive warning spew every time that a user of library X tries to build it as a dependency.
  3. The author of library X fixes all the warnings. But now their library only works with Rust 1.6, and it can no longer be built using the system Rust on Amazon Linux/RHEE/Ubuntu LTS 16.04.
  4. If I want the latest bug fixes for library X, I need to start upgrading lots of other things.

As long as Rust keeps churning, it forces everybody to pay more attention to rustc and std, and less attention to interesting crates and applications. And I’d much rather live with an occasional odd API than spend my time dealing with cascading updates across the ecosystem.

So :+1: for occasional, careful deprecations of genuinely awful APIs. But :-1: for “aggressively” deprecating large swaths of the Rust API. My main concern here is that if the word “aggressively” is actually included in the official policy, it will be used as a rationale for a lot of API tinkering that privileges an ideal vision of the std library over actual Rust applications. The single best feature offered by infrastructure software is staying put, so other people can build around it.

6 Likes