6 ms·
Using #[non_exhaustive] on enums is going to be general bad practice, just as many in c++ consider ‘default’ switch cases bad practice. When a new state is add
by randyrand 7y ago
Using #[non_exhaustive] on enums is going to be general bad practice, just as many in c++ consider ‘default’ switch cases bad practice.
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.
- steveklabnik 7y agoYes, that’s the trade off. Like all trade offs, it makes sense sometimes, but you’re right that it’s not to be used in most cases.
- gdxhyrd 7y agoYeah, I don't like it at all. 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.
- cesarb 7y ago> 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. 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 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.
- elihu 7y agoPerhaps Rust could add a #[exhaustive] pragma at the point where the enum is being pattern matched to over-ride the libary's #[non-exhaustive] pragma, for users that really do want their code not to compile when an enum is extended.
- est31 7y agoThis proposal has been made in the tracking issue already, yet it hasn't been picked up on yet: https://github.com/rust-lang/rust/issues/44109#issuecomment-521781237 https://github.com/rust-lang/rust/issues/44109#issuecomment-...
- csomar 7y agoI agree. That's what Semver is for. If you are adding new variants to your enum that can break code for your dependents, then increment your version accordingly.
- IshKebab 7y agoRight but some enums are designed to allow new types to be added. For example suppose you add another image format to the `image` library: https://docs.rs/image/0.23.0-preview.0/image/enum.ImageFormat.html 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?
- deleted 7y ago[deleted]
- Guvante 7y agoThat is why there are two versions available. The default matches the behavior you describe and is the default because for small projects it is the right decision. 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.
- thayne 7y agoThe `Ordering` example given in the announcement shows another use-case. User code typically won't pattern match on that enum anyway, it will usually just be passed as an argument to atomic functions. And it may be desirable to add another type of ordering as rust's memory model evolves.
- kibwen 7y agoError enums are precisely the target for this attribute. Servo's URL parser is a great example, as it currently uses a dummy variant that is hidden from the documentation in order to discourage people from trying to exhaustively match over it: https://github.com/servo/rust-url/blob/7d2c9d6ceb3307a3fad4c4bcb7f5b07b5e24c755/src/parser.rs#L109-L119 https://github.com/servo/rust-url/blob/7d2c9d6ceb3307a3fad4c...
- bcrosby95 7y agoSince the developer of a library has no insight into my application, they have no idea how important an exhaustive match is or isn't in any given piece of code I'm writing. This is a decision library users should be making, not library writers.
- skybrian 7y agoIt seems like the main issue is consent. If you're using a library in a way the author didn't agree to, that's fine, but you don't get any guarantee it will keep working after an upgrade. If you want to do that, you could edit your own copy of the library's source code and nobody will mind. Maybe that's enough? It seems like you shouldn't be able to publish a crate where you're using an upstream library in a way they don't consent to, because now you're involving others in this dispute. A basic requirement for publishing to a shared open source ecosystem should be that you're resolving any disputes you have with upstream libraries and not just going your own way.
- kibwen 7y agoThis ties into what I believe is going to be one of the biggest themes of programming language development in the 2020s: first-class language features that allows defensive libraries to make changes that don't cause breakage in downstream users. Right now I'd say Swift is the poster child of this movement; many of its language features are head-scratchers until you realize that they exist to keep applications compiling and on a clean upgrade path even when their dependencies are actively changing. 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.
- dilap 7y agoI'd be curious to hear more about these Swift features. (Context: used to do iOS dev w/ obj-c; haven't used Swift, but keeping an eye on it, mostly out of curiosity.)
- amaranth 7y agoThere was an interesting write up done recently about Swift ABI features that enable this and why Rust doesn't/can't do similar things due to different design goals. https://gankra.github.io/blah/swift-abi/ https://gankra.github.io/blah/swift-abi/
- breatheoften 7y agoIts not a small change ... binary compatibility has essentially meant “c compatible” for the last several decades ... will any of the new languages currently attempting to implement a binary comparability model succeed in shifting this in a meaningful way?
- zozbot234 7y agoComplete binary compatibility across modules for open records/sums (this is what #[non_exhaustive] boils down to) is quite non-trivial. You end up going through a level of indirection, kind of like objects in Python/Ruby etc. It's not a disaster because it specifically applies to the open case, which ought to be rare; but it's something to be aware of.
- lilyball 7y agoSwift has a really good solution to this, which is an attribute @unknown that you put on the default case, and this attribute produces a warning if there are any known enum variants that would match this case. This way you're future-compatible but the warnings tell you when you need to revisit the code. I'm pretty disappointed that Rust didn't copy this.
- Buttons840 7y agoCan't you just do that with a catch all pattern match?
- jeremyjh 7y agoIt’s only relevant if you have a catch-all match. The idea is to get a compiler warning when new patterns become available instead of a runtime warning.
- steveklabnik 7y agoThis is a good idea for a lint!
- cryptonector 7y agoYou need a wildcard match which allows you to fail... at run-time. I see what you mean.