I know how hard this is to pull off and I am not trying to say they have done a poor job; I genuinely want to know what others consider a breaking change.
I know how hard this is to pull off and I am not trying to say they have done a poor job; I genuinely want to know what others consider a breaking change.
[1]: https://github.com/rust-lang-nursery/cargobomb [2]: https://www.rust-lang.org/en-US/friends.html
use std::cell::*;
struct MoveCell;
We then add a new type to std::cell, also named MoveCell. Your code will now fail to compile, as the two structures conflict.This kind of breakage can still happen, even though we care a lot about stability.
The guiding principle is "it should not be painful to update the compiler", and I think we do as good a job as we can making sure that that's true.
It seems like the vast majority of cases you've enumerated are in the category of "code won't compile". Perhaps some have a knee-jerk reaction against this, but to me this is kind of incompatibility seems much more preferable to the other, especially if the update required to the code is trivial and obvious.
- Patching up soundness holes causing previously-unsound code to error (https://github.com/rust-lang/rust/pull/43651 is an example)
- Changing inference in a way that may require new annotations
- Adding types and things to modules which may cause clashes on glob imports (steve's example above)
- Adding methods to types (this may cause code calling trait methods to require disambiguation via the fully qualified UFCS syntax)
- Adding new trait impls (similar disambiguation issues)
- Lint changes (If you `#[deny(lint)]` a lint, this may cause compilation failures). Cargo passes --cap-lints=warn to dependencies so a lint change will at most cause your dependencies to spew extra warnings and cause your code to stop compiling, in a way that's trivial to fix (allow the lint, or fix the warning).
In general for all releases we run cargobomb to look out for breakages, both "okay" ones and not okay ones. Additionally, for the first two kinds of breakages (and other miscellaneous iffy changes) we proactively cargobomb. Even if something is technically allowed by our stability rules we want to make sure that practically speaking it has minimal impact.
For example, one of the timely traits had `min` and `max` methods, as it corresponded to a type with upper and lower bounds. Rust added `min` and `max` convenience methods to all implementors of `Ord` (which this type was), which now means that anyone who wants to use the type needs to use UFCS. Or, I issue a major break so that their lives don't suck (now `minimum` and `maximum`).
The Rust change was classified as fixing a "papercut", so that folks wouldn't have to `use std::cmp::{min, max};`, but it's a pain in the arse when it's done so casually.
Feel free to factor in that approximately five people use crates that I write, and most of them have bigger problems than Rust breakage. :)
Isn't this more-or-less true of any statically-typed language? I can't think of any off the top of my head that have the behavior of implicitly re-defining a type when a new definition is encountered after another definition was previously brought into scope.
Even C isn't that sadistic:
$ echo "\
> #include <time.h>
> struct tm {};" | cc -ansi -xc -
<stdin>:2:8: error: redefinition of 'struct tm'
struct tm {};
^~
In file included from <stdin>:1:0:
/usr/include/time.h:74:8: note: originally defined here
struct tm {
^~ use std::cell::*;
struct Cell;
works: Local names shadow glob imports.