<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">Hi Jordan,<div class=""><br class=""></div><div class="">let's say I'm writing my custom number formatter and I switch over&nbsp;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.</div><div class=""><br class=""></div><div class="">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, ...).</div><div class=""><br class=""></div><div class="">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...</div><div class=""><br class=""><div><blockquote type="cite" class=""><div class="">On Aug 10, 2017, at 1:57 AM, Jordan Rose &lt;<a href="mailto:jordan_rose@apple.com" class="">jordan_rose@apple.com</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class="">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?<div class=""><br class=""></div><div class="">(There's an additional piece about how to handle cases with different availability—there's nowhere obvious to write the #available.)</div><div class=""><br class=""></div><div class="">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 &nbsp;60 or so NS_ENUMs in Foundation, only 6 of them are ones I would definitely mark "closed":</div><div class=""><br class=""></div><div class="">-&nbsp;NSComparisonResult</div><div class="">-&nbsp;NSKeyValueChange /&nbsp;NSKeyValueSetMutationKind</div><div class="">- NSRectEdge</div><div class="">-&nbsp;NSURLRelationship</div><div class="">- <i class="">maybe</i>&nbsp;NSCalculationError</div><div class=""><br class=""></div><div class="">There are a few more, like&nbsp;NSURLHandleStatus, where I could see someone wanting to exhaustively switch as well, but the main point is that there <i class="">is</i>&nbsp;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 <i class="">all</i>&nbsp;of them.</div><div class=""><br class=""></div><div class="">(Note that NSComparisonResult is technically not part of Foundation; it lives in the ObjectiveC module as /usr/include/objc/NSObject.h.)</div><div class=""><br class=""></div><div class="">Jordan<br class=""><div class=""><br class=""><div class=""><br class=""><blockquote type="cite" class=""><div class="">On Aug 8, 2017, at 21:53, Charlie Monroe &lt;<a href="mailto:charlie@charliemonroe.net" class="">charlie@charliemonroe.net</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">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&nbsp;<div class=""><br class=""></div><div class="">default:</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>fatalError()</div><div class=""><br class=""></div><div class="">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.</div><div class=""><br class=""></div><div class="">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.</div><div class=""><br class=""></div><div class="">I'd personally expand this proposal by introducing switch! (with the exclamation mark) which would allow to treat open enums as closed. Example:</div><div class=""><br class=""></div><div class="">// Imported from ObjC</div><div class="">open enum NSAlert.Style { ... }</div><div class=""><br class=""></div><div class="">switch! alert.style {</div><div class="">case .warning:</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>// ...</div><div class="">case .informational:</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>// ...</div><div class="">case .critical:</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>// ...</div><div class="">}</div><div class=""><br class=""></div><div class="">The force-switch would implicitely create the default label crashing, logging the rawValue of the enum.</div><div class=""><br class=""></div><div class="">Thoughts?</div><div class=""><br class=""><div class=""><blockquote type="cite" class=""><div class="">On Aug 9, 2017, at 12:28 AM, Jordan Rose via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class=""><div dir="auto" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class="">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:<div class=""><br class=""></div><div class="">- 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.</div><div class="">- 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?</div><div class="">- Adding a new case to a&nbsp;<i class="">Swift</i>&nbsp;enum in a library breaks any client code that was trying to switch over it.</div><div class=""><br class=""></div><div class="">(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.)</div><div class=""><br class=""></div><div class="">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&nbsp;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&nbsp;Strings in it.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><b class="">Behavior</b><br class=""><br class="">I think there's certain behavior that is probably not&nbsp;<i class="">terribly</i>&nbsp;controversial:<br class=""><br class="">- 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&nbsp;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.)<br class="">- When I define Swift enums in the current framework, there's obviously no compatibility issues; we should allow exhaustive switches.<br class=""><br class="">Everything else falls somewhere in the middle, both for enums defined in Objective-C:<br class=""><br class="">- 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&nbsp;still be private cases defined in a .m file?<br class="">- If there's an Objective-C enum in&nbsp;<i class="">another</i>&nbsp;framework (that I built locally with Xcode, Carthage, CocoaPods, SwiftPM, etc.), should it allow exhaustive switching, because&nbsp;there are no&nbsp;<i class="">binary</i>&nbsp;compatibility issues, or not, because there may be&nbsp;<i class="">source</i>&nbsp;compatibility issues? We'd really like adding a new enum case to&nbsp;<i class="">not</i>&nbsp;be a&nbsp;breaking change even at the source level.<br class="">- 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&nbsp;might be non-modular content I've used the bridging header to import?<br class=""><br class="">And in Swift:<br class=""><br class="">- 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&nbsp;may be source compatibility issues? Again, we'd really like adding a new enum case to&nbsp;<i class="">not</i>&nbsp;be a breaking change even at the source level.<br class=""><br class=""></div><div class="">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&nbsp;you&nbsp;must&nbsp;have a 'default' in a switch". In previous (in-person) discussions&nbsp;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&nbsp;also&nbsp;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&nbsp;anyway. (Think about Apple's frameworks again.) I don't have a great answer, though.<br class=""><br class="">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&nbsp;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.<br class=""><br class=""></div><div class=""><br class=""></div><div class=""><b class="">Terminology</b></div><div class=""><b class=""><br class=""></b></div><div class="">The "<a href="http://jrose-apple.github.io/swift-library-evolution/" class="">Library Evolution</a>" 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:</div><div class=""><br class=""></div><div class="">- For classes, "open" and "non-open" restrict what the <i class="">client</i> can do. For enums, it's more about providing the client with additional guarantees—and "non-open" is the one with more guarantees.</div><div class="">- 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".</div><div class=""><br class=""></div><div class="">That said, Clang now has an 'enum_extensibility' attribute that does take 'open' or 'closed' as an argument.</div><div class=""><br class=""></div><div class="">On Matthew's thread, a few other possible names came up, though mostly only for the "closed" case:</div><div class=""><br class=""></div><div class="">- '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.</div><div class="">- 'locked': reasonable, but not a standard term, and could get confused with the concurrency concept</div><div class="">- 'exhaustive': matches how we've been explaining it (with an "exhaustive switch"), but it's not exactly the <i class="">enum</i>&nbsp;that's exhaustive, and it's a long keyword to actually write in source.</div><div class=""><br class=""></div><div class="">- 'extensible': matches the Clang attribute, but also long</div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">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 <i class="">really like to find some</i>.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><b class="">Proposal</b></div><div class=""><b class=""><br class=""></b></div><div class="">Just to have something to work off of, I propose the following:</div><div class=""><br class=""></div><div class="">1. All enums (NS_ENUMs) imported from Objective-C are "open" unless they are declared "non-open" in some way (likely using the&nbsp;enum_extensibility attribute mentioned above).</div><div class="">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&nbsp;an app, where binary compatibility is a concern.<br class="">3. All public Swift enums in modules compiled from source have the option to be either "open" or "closed".</div><div class="">4. In Swift 5 mode, a public enum should be <i class="">required</i> 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".)</div><div class="">5. None of this affects non-public enums.</div><div class=""><br class=""></div><div class="">(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.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><b class="">Why now?</b></div><div class=""><br class=""></div><div class="">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 <i class="">have</i>&nbsp;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".</div><div class=""><br class=""></div><div class="">I've written quite enough at this point. Looking forward to feedback!</div><div class="">Jordan<br class=""></div></div></div>_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class=""><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" class="">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br class=""></div></blockquote></div><br class=""></div></div></div></blockquote></div><br class=""></div></div></div></div></blockquote></div><br class=""></div></body></html>