When a new state is added to an enum, we want the code to not compile so that we can fix all the places that need updating.
However sometimes you are a dependency and you want to give up this restriction to gain the ability to add things without having to bump your major version number.
By far the most common example is error enums which don't necessarily need all of their downstream crates to handle every error, they likely are bucketing most of them anyway and non_exhaustive ensures they support that.
This is a decision library users should be making, not library writers.
Of course, the trade-off is obviously that by choosing to make things continue to compile when something changes, you are no longer causing things to fail to compile when something changes. I'm uncertain how this tension will be resolved in the long run.
(Context: used to do iOS dev w/ obj-c; haven't used Swift, but keeping an eye on it, mostly out of curiosity.)
Yes, some enums are intended to be expanded. Still, as long as something is a compile-time error, I don't care about having to update my code when I choose to upgrade compilers.
In fact, even if the enum is intended to be expanded, I prefer to hear about new features and changes on APIs I actually use in a given project. It allows me to review mu choices and many times in complex APIs there are new options or flags that are useful to know about and otherwise get ignored. It allows me to update all callers as needed.
I have heard too many times the backwards compatibility story in the C++ world and I never valued it for things that are errors rather than behavior changes. It makes the libraries and the language way too rigid and makes evolving it a pain.
As long as you don't change the meaning of code that compiles cleanly, feel free to change things.
If you really want to keep code compilable because you are doing a chance that you believe will be a PITA, you could always offer a (truly) automated tool.
It's not just your code; it could be the code of any of your dependencies (or their dependencies, recursively). Besides, the Rust team does intend to keep older code compiling unchanged on newer releases of the Rust compiler, unless the breakage is caused by fixing a soundness hole.
The best example of a non-exhaustive enum, in my opinion, would be std::io::ErrorKind (https://doc.rust-lang.org/std/io/enum.ErrorKind.html). Several Rust releases ago, I added a new variant to that enum (the last one, UnexpectedEof, used by std::io::Read::read_exact - notice how that variant comes after "Other", which used to be the last one). If that enum were not non-exhaustive (non-exhaustive enums already existed since Rust 1.0, though using a doc-hidden trick instead of formal compiler support), I would not have been able to add that new variant, since it would risk breaking anything which matched on every variant of that enum.
For example suppose you add another image format to the `image` library:
https://docs.rs/image/0.23.0-preview.0/image/enum.ImageForma...
What's more annoying - a major version bump every time you support a new format, or not being able to exhaustively match on all 10 formats?
Congrats to everyone who has been contributing to Rust, it's definitely moving the language forward!
The progress is still cool though.
Plus there isn't really anything else in the market that directly competes with it outside of what it tries to replace (C/C++).
For example: https://msrc-blog.microsoft.com/2019/11/07/using-rust-in-win...
To me, Rust "feels" like one of those languages that will stealthily become very important at least in certain segments where security, performance, C ABI compatibility (dll/so/dylib) and zero runtime are required.
I do a fair amount of integration with C, and the work the rust team is doing for procedural macros has helped a lot. Being able to use them in an extern blocks will surely help more.
Like many, I have a wish list that I complain about sometimes, but I feel bad about complaining because rust has done so much to improve my use case I don't know where to begin. I should write up a blog post about all of the difficulty I had, and how each release since around 1.28 has introduced features that solved these problems one by one.
To me, Rust/C interop is the killer feature. Not only is it a low-level language that can be used instead of C, it can also be used to extend C applications. I'm sure it was not easy to do this well, but what a great strategic decision by the rust team to spend the effort on it!
Release notes are helpful -- when you see something added related to C, it's a strong hint to read about the new feature.
Macro features are important because rust has sophisticated macros and C macros are closer to text replacement. So when trying to emulate a C header, you need to do a lot of macro magic in rust sometimes.
Sorry that I don't have more to offer from personal experience.
Here's the one for C:
I don't think this is necessary. The attribute in question is applied to an enum variant, and that variant's constructor is then given only crate-wide visibility. This looks to be simply a compiler-enforced codification of the pattern you're describing.
Thanks for sharing!
Instead, this is what will be elegant:
pub enum SearchOption {
Left,
Full,
}
pub enum SearchOptionBeta:SearchOption {
Rigth,
}
Yep, extending struct and enums.This mean MATCH touch "SearchOption" as the "stable" API and the inner crate touch SearchOptionBeta. When SearchOptionBeta need magic is that it match SearchOption too. Later is just a matter of replace one for the other.
And this is even useful for more than to stabilize certain fields or arms...
pub enum SearchOption {
Left,
Full,
Beta(SearchOptionBeta),
}
#[non_exhaustive]
pub enum SearchOptionBeta {
Right
}If enums were abled to be extended:
match e: SearchOptionBeta {
SearchOptionBeta:: Left, Rigth, Full
}Of course it's an error, as Bar doesn't have a field called ,,a''