<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class="">Both you and Vladimir are bringing up this point, with Vladimir explicitly suggesting a "future" case that's different from "default". Again, the pushback we get here is that the "future" case is untestable…but maybe that's still an option worth having. (At the very least, it's worth recording in any eventual proposal why we <i class="">don't</i>&nbsp;have it, and it could be added later if it turns out that was wrong.)<div class=""><br class=""></div><div class="">Thank you both for pushing on it.</div><div class=""><br class=""></div><div class="">Jordan<br class=""><div class=""><br class=""><div><br class=""><blockquote type="cite" class=""><div class="">On Aug 9, 2017, at 21:55, 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="">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 class=""><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></div></div></blockquote></div><br class=""></div></div></body></html>