I have been thinking about this issue a bit more.
I think that may apply for interfaces (though I even think that deprecations and replacements are possible there), but it does not generally hold for "code" as in "implementations".
Let's look into this further:
Indeed, most types should be consistent over all versions. That is, at least their data structure must be identical/compatible, but not their API. Imagine RwLock
was just a struct with almost no methods at all, and the respective API (such as .read
and .write
) was provided by an extension trait in std::v1_ext
. Then we could easily have some crates use std::v2_ext
instead of std::v1_ext
, while all components use the same underlying data type. The methods could still have differing return types (e.g. some might return a Result<RwLockReadGuard>
while others return an RwLockReadGuard
).
This is just a thought-experiment; I don't think it's currently feasible to do this, as it would change a lot on how the standard library was used, and maybe extension traits have some other downsides. Plus, most methods are implemented directly on the structs, so that's also difficult to change without a huge effort, and not sure if that'd be really worth it. But it may be something worth to keep in mind when designing libraries? Maybe the value of extension traits is underestimated, and perhaps there could be even more syntactic sugar for them? (Just ideas so far.)
Another thought experiment: What if the language was extended by a feature to provide data-type compatibility, such that their representation in memory can be the same, but the provided interface may differ. That would allow a new std
version while maintaining compatibility between something like std::v1::sync::RwLock
and std::v2::sync::RwLock
, while the latter provides a different interface. Of course, that would require a language extension if std::v1::sync::RwLock
had the API implemented directly on the type and not in a separate trait.
I guess that's true for shared data types, but not true for the whole implementation and some intermediary types (as I tried to outline above).
As pointed out in my previous post, using platform's standard APIs may lead to non-portability when relying on certain behavior. I would like to give two examples:
In case of RwLock
, this is documented explicitly, so there is no real problem here (if programmers follow the docs!), but I would like to note that simplicity or simple fallbacks to the OS may be contradictory to the goal to provide a portable std
library.
I am mostly happy with the standard library, but I see how there are certain limitations and maybe I indeed expected a bit too much. I still feel like it would be a win to have certain extensions to the standard library (e.g. crates) being "officially endorsed" by the Rust team. That wouldn't need to be done by the same group of people who maintain std
, of course. Oh, I just saw there has been a ticket on "official" crates in 2014, so the idea isn't new. It was closed in 2015 with a reference to the nursery (I guess that's also where the Rust Cookbook comes from, that I mentioned above?) and the rust-lang organization (that means the Rust Foundation?).
Last but not least, I'd say something about the Python2/3 issue: Even if it always serves as a deterrent example, I would like to add that I appreciate and admire the Python developers for having made decisions to finally get rid of some old stuff. Yes, it was painful, but I do (personally) enjoy writing in Python 3 instead of 2. I don't want to say it's good to break things, but if there was a way to not break things while fixing mistakes from the past (I gave some ideas above), that would be wonderful.
Rust's editions have shown how this works for the language itself, and we can also fix mistakes by replacing 3rd party crates. But there is a gap in the middle:
- Mistakes in the language: fixable through editions
- Mistakes in the
std
lib: not fixable?
- Mistakes in 3rd party crates: fixable through breaking changes