Hi Jordan, let's say I'm writing my custom number formatter and I switch over NSNumberFormatterStyle (NumberFormatter.Style in Swift) - the question always is what to do with the default case - it's a value that I am not programmatically counting with. I would personally just put in fatalError with a description that it was passed an unhandled style. Listing all enums in Foundation, I can see using most of them this way.
I personally have most of my switches exhaustive, mainly for the sake of being warned/error'ed when a new case is introduced - I've just done a quick search through my projects and I use default: usually for switching over non-enums (strings, object matching, ints, ...). Maybe I'm in the minority here... Seemed like a good practice to me - usually the enum doesn't have but a few items on the list and you usually don't handle just 1-2 cases in your switch, which makes the default label save you 1-2 lines of code that can save you from unnnecessarily crashing during runtime... > On Aug 10, 2017, at 1:57 AM, Jordan Rose <jordan_r...@apple.com> wrote: > > Hi, Charlie. This is fair—if you're switching over an open enum at all, > presumably you have a reason for doing so and therefore might want to handle > all known cases every time you update your SDK. However, I think in practice > that's going to be rare—do you have examples of exhaustive switches on SDK > enums that exist in your own app? > > (There's an additional piece about how to handle cases with different > availability—there's nowhere obvious to write the #available.) > > I suspect marking SDK enums "closed" will be much easier than nullability, > simply because there are so few of them. Here's some data to that effect: out > of all 60 or so NS_ENUMs in Foundation, only 6 of them are ones I would > definitely mark "closed": > > - NSComparisonResult > - NSKeyValueChange / NSKeyValueSetMutationKind > - NSRectEdge > - NSURLRelationship > - maybe NSCalculationError > > There are a few more, like NSURLHandleStatus, where I could see someone > wanting to exhaustively switch as well, but the main point is that there is a > clear default for public enums, at least in Objective-C, and that even in a > large framework it's not too hard to look at all of them. > > (Note that NSComparisonResult is technically not part of Foundation; it lives > in the ObjectiveC module as /usr/include/objc/NSObject.h.) > > Jordan > > >> On Aug 8, 2017, at 21:53, Charlie Monroe <char...@charliemonroe.net >> <mailto:char...@charliemonroe.net>> wrote: >> >> While I agree with the entire idea and would actually use behavior like this >> in a few instances, I feel that in most cases, you would simply put >> >> default: >> fatalError() >> >> The huge downside of this is that you no longer get warned by the compiler >> that you are missing a case that was added - a common thing I personally do >> (and I have a feeling I'm not alone) - add an enum case, build the app, see >> what broke and fix it - as you get warnings/errors about the switch not >> being exhaustive. You find this out during runtime (if you're lucky), >> otherwise your end user. >> >> As you've noted all enums from ObjC would need to be marked with an >> annotation marking if they are closed - which given the way nullability is >> still missing in many frameworks out there, I think would take years. >> >> I'd personally expand this proposal by introducing switch! (with the >> exclamation mark) which would allow to treat open enums as closed. Example: >> >> // Imported from ObjC >> open enum NSAlert.Style { ... } >> >> switch! alert.style { >> case .warning: >> // ... >> case .informational: >> // ... >> case .critical: >> // ... >> } >> >> The force-switch would implicitely create the default label crashing, >> logging the rawValue of the enum. >> >> Thoughts? >> >>> On Aug 9, 2017, at 12:28 AM, Jordan Rose via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>> >>> Hi, everyone. Now that Swift 5 is starting up, I'd like to circle back to >>> an issue that's been around for a while: the source compatibility of enums. >>> Today, it's an error to switch over an enum without handling all the cases, >>> but this breaks down in a number of ways: >>> >>> - A C enum may have "private cases" that aren't defined inside the original >>> enum declaration, and there's no way to detect these in a switch without >>> dropping down to the rawValue. >>> - For the same reason, the compiler-synthesized 'init(rawValue:)' on an >>> imported enum never produces 'nil', because who knows how anyone's using C >>> enums anyway? >>> - Adding a new case to a Swift enum in a library breaks any client code >>> that was trying to switch over it. >>> >>> (This list might sound familiar, and that's because it's from a message of >>> mine on a thread started by Matthew Johnson back in February called >>> "[Pitch] consistent public access modifiers". Most of the rest of this >>> email is going to go the same way, because we still need to make progress >>> here.) >>> >>> At the same time, we really like our exhaustive switches, especially over >>> enums we define ourselves. And there's a performance side to this whole >>> thing too; if all cases of an enum are known, it can be passed around much >>> more efficiently than if it might suddenly grow a new case containing a >>> struct with 5000 Strings in it. >>> >>> >>> Behavior >>> >>> I think there's certain behavior that is probably not terribly >>> controversial: >>> >>> - When enums are imported from Apple frameworks, they should always require >>> a default case, except for a few exceptions like NSRectEdge. (It's Apple's >>> job to handle this and get it right, but if we get it wrong with an >>> imported enum there's still the workaround of dropping down to the raw >>> value.) >>> - When I define Swift enums in the current framework, there's obviously no >>> compatibility issues; we should allow exhaustive switches. >>> >>> Everything else falls somewhere in the middle, both for enums defined in >>> Objective-C: >>> >>> - If I define an Objective-C enum in the current framework, should it allow >>> exhaustive switching, because there are no compatibility issues, or not, >>> because there could still be private cases defined in a .m file? >>> - If there's an Objective-C enum in another framework (that I built locally >>> with Xcode, Carthage, CocoaPods, SwiftPM, etc.), should it allow exhaustive >>> switching, because there are no binary compatibility issues, or not, >>> because there may be source compatibility issues? We'd really like adding a >>> new enum case to not be a breaking change even at the source level. >>> - If there's an Objective-C enum coming in through a bridging header, >>> should it allow exhaustive switching, because I might have defined it >>> myself, or not, because it might be non-modular content I've used the >>> bridging header to import? >>> >>> And in Swift: >>> >>> - If there's a Swift enum in another framework I built locally, should it >>> allow exhaustive switching, because there are no binary compatibility >>> issues, or not, because there may be source compatibility issues? Again, >>> we'd really like adding a new enum case to not be a breaking change even at >>> the source level. >>> >>> Let's now flip this to the other side of the equation. I've been talking >>> about us disallowing exhaustive switching, i.e. "if the enum might grow new >>> cases you must have a 'default' in a switch". In previous (in-person) >>> discussions about this feature, it's been pointed out that the code in an >>> otherwise-fully-covered switch is, by definition, unreachable, and >>> therefore untestable. This also isn't a desirable situation to be in, but >>> it's mitigated somewhat by the fact that there probably aren't many >>> framework enums you should exhaustively switch over anyway. (Think about >>> Apple's frameworks again.) I don't have a great answer, though. >>> >>> For people who like exhaustive switches, we thought about adding a new kind >>> of 'default'—let's call it 'unknownCase' just to be able to talk about it. >>> This lets you get warnings when you update to a new SDK, but is even more >>> likely to be untested code. We didn't think this was worth the complexity. >>> >>> >>> Terminology >>> >>> The "Library Evolution >>> <http://jrose-apple.github.io/swift-library-evolution/>" doc (mostly >>> written by me) originally called these "open" and "closed" enums ("requires >>> a default" and "allows exhaustive switching", respectively), but this >>> predated the use of 'open' to describe classes and class members. Matthew's >>> original thread did suggest using 'open' for enums as well, but I argued >>> against that, for a few reasons: >>> >>> - For classes, "open" and "non-open" restrict what the client can do. For >>> enums, it's more about providing the client with additional guarantees—and >>> "non-open" is the one with more guarantees. >>> - The "safe" default is backwards: a merely-public class can be made >>> 'open', while an 'open' class cannot be made non-open. Conversely, an >>> "open" enum can be made "closed" (making default cases unnecessary), but a >>> "closed" enum cannot be made "open". >>> >>> That said, Clang now has an 'enum_extensibility' attribute that does take >>> 'open' or 'closed' as an argument. >>> >>> On Matthew's thread, a few other possible names came up, though mostly only >>> for the "closed" case: >>> >>> - 'final': has the right meaning abstractly, but again it behaves >>> differently than 'final' on a class, which is a restriction on code >>> elsewhere in the same module. >>> - 'locked': reasonable, but not a standard term, and could get confused >>> with the concurrency concept >>> - 'exhaustive': matches how we've been explaining it (with an "exhaustive >>> switch"), but it's not exactly the enum that's exhaustive, and it's a long >>> keyword to actually write in source. >>> >>> - 'extensible': matches the Clang attribute, but also long >>> >>> >>> I don't have better names than "open" and "closed", so I'll continue using >>> them below even though I avoided them above. But I would really like to >>> find some. >>> >>> >>> Proposal >>> >>> Just to have something to work off of, I propose the following: >>> >>> 1. All enums (NS_ENUMs) imported from Objective-C are "open" unless they >>> are declared "non-open" in some way (likely using the enum_extensibility >>> attribute mentioned above). >>> 2. All public Swift enums in modules compiled "with resilience" (still to >>> be designed) have the option to be either "open" or "closed". This only >>> applies to libraries not distributed with an app, where binary >>> compatibility is a concern. >>> 3. All public Swift enums in modules compiled from source have the option >>> to be either "open" or "closed". >>> 4. In Swift 5 mode, a public enum should be required to declare if it is >>> "open" or "closed", so that it's a conscious decision on the part of the >>> library author. (I'm assuming we'll have a "Swift 4 compatibility mode" >>> next year that would leave unannotated enums as "closed".) >>> 5. None of this affects non-public enums. >>> >>> (4) is the controversial one, I expect. "Open" enums are by far the common >>> case in Apple's frameworks, but that may be less true in Swift. >>> >>> >>> Why now? >>> >>> Source compatibility was a big issue in Swift 4, and will continue to be an >>> important requirement going into Swift 5. But this also has an impact on >>> the ABI: if an enum is "closed", it can be accessed more efficiently by a >>> client. We don't have to do this before ABI stability—we could access all >>> enums the slow way if the library cares about binary compatibility, and add >>> another attribute for this distinction later—but it would be nice™ (an easy >>> model for developers to understand) if "open" vs. "closed" was also the >>> primary distinction between "indirect access" vs. "direct access". >>> >>> I've written quite enough at this point. Looking forward to feedback! >>> Jordan >>> _______________________________________________ >>> swift-evolution mailing list >>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >> >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution