<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body dir="auto"><div></div><div>Thanks for this extended rationale.</div><div><br></div><div>I would pin this post if I could. Or extend the "motivation" section of the proposal with it.</div><div><br></div><div>Gwendal</div><div><br>Le 5 janv. 2018 à 01:38, Jordan Rose via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>&gt; a écrit&nbsp;:<br><br></div><blockquote type="cite"><div><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><div class="">Hi, Dave. You're right, all these points are worth addressing. I'm going to go in sections.</div><div class=""><br class=""></div><div class=""><blockquote type="cite" class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class="">This whole “unexpected case” thing is only a problem when you’re linking libraries that are external to/shipped independently of&nbsp;your app. Right now, the *only* case where this might exist is Swift on the server. We *might* run in to this in the future once the ABI stabilizes and we have the Swift libraries shipping as part of iOS/macOS/Linux. Other than this, unexpected enum cases won’t really be a problem developers have to deal with.</div></div></blockquote></div><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><br class=""></div><div class="">I wish this were the case, but it is not. Regardless of what we do for <i class="">Swift</i>&nbsp;enums, we are in dire need of a fix for <i class="">C</i>&nbsp;enums. Today, if a C enum doesn't have one of the expected values, the behavior is undefined in the C sense (as in, type-unsafe, memory-unsafe, may invoke functions that shouldn't be invoked, may not invoke functions that should be invoked, etc).</div><div class=""><br class=""></div><div class="">Obviously that's an unacceptable state of affairs; even without this proposal we would fix it so that the program will deterministically trap instead. This isn't perfect because it results in a (tiny) performance and code size hit compared to C, but it's better than leaving such a massive hole in Swift's safety story.</div><div class=""><br class=""></div><div class="">The trouble is that many enums—maybe even <i class="">most</i>&nbsp;enums—in the Apple SDK really are expected to grow new cases, and the Apple API authors rely on this. Many of those—probably most of them—are the ones that Brent Royal-Gordon described as "opaque inputs", like UIViewAnimationTransition, which you're unlikely to switch over but which the compiler should handle correctly if you do. Then there are the murkier ones like SKPaymentTransactionState.</div><div class=""><br class=""></div><div class="">I'm going to come dangerously close to <i class="">criticizing Apple</i>&nbsp;and say&nbsp;I have a lot of sympathy for third-party developers in the SKPaymentTransactionState case. As Karl Wagner said, there wasn't really any way an existing app could handle that case well, even if they <i class="">had</i>&nbsp;written an 'unknown case' handler. So what could the StoreKit folks have done instead? They can't tell themselves whether your app supports the new case, other than the heavy-handed "check what SDK they compiled against" that ignores the possibility of embedded binary frameworks. So maybe they should have added a property "supportsDeferredState" or something that would have to be set before the new state was returned.</div><div class=""><br class=""></div><div class="">(I'll pause to say I don't know what consideration went into this API and I'm going to avoid looking it up to avoid perjury. This is all hypothetical, for the <i class="">next</i>&nbsp;API that needs to add a case.)</div><div class=""><br class=""></div><div class="">Let's say we go with that, a property that controls whether the new case is ever passed to third-party code. <i class="">Now the new case exists, and new code needs to switch over it.</i>&nbsp;At the same time, <i class="">old code needs to continue working.</i>&nbsp;The new enum case exists, and so even if it <i class="">shouldn't</i>&nbsp;escape into old code that doesn't know how to handle it, the behavior needs to be defined if it does. Furthermore, the old code needs to continue working <i class="">without source changes,</i>&nbsp;because updating to a new SDK must not break existing code.&nbsp;(It can introduce new warnings, but even that is something that should be considered carefully.)</div><div class=""><br class=""></div><div class="">So: this proposal is designed to handle the use cases both for Swift library authors to come and for C APIs today, and in particular Apple's Objective-C SDKs and how they've evolved historically.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">There's another really interesting point in your message, which Karl, Drew Crawford, and others also touched on.</div><div class=""><br class=""></div><div class=""><blockquote type="cite" class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class="">Teaching the compiler/checker/whatever about the linking semantics of modules. For modules that are packaged inside the final built product, there is no need to deal with any unexpected cases, because we already have the exhaustiveness check appropriate for that scenario (regardless of whether the module is shipped as a binary or compiled from source). The app author decides when to update their dependencies, and updating those dependencies will produce new warnings/errors as the compiler notices new or deprecated cases. This is the current state of things and is completely orthogonal to the entire discussion.</div></div></blockquote><div class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div class=""><br class=""></div></div></div></div></div></div><div class="">This keeps sneaking into discussions and I hope to have it formalized in a proposal soon. On the library side, we <i class="">do</i>&nbsp;want to make a distinction between "needs binary compatibility" and "does not need binary compatibility". Why? Because we can get much better <i class="">performance</i>&nbsp;if we know a library is never going to change. A class will not acquire new dynamic-dispatch members; a stored property will not turn into a computed property; a struct will not gain new stored properties. None of those things affect how client code is <i class="">written,</i>&nbsp;but they do affect what happens at run-time.</div><div class=""><br class=""></div><div class="">Okay, so should we use this as an indicator of whether an enum can grow new cases? (I'm going to ignore C libraries in this section, both because they don't have this distinction and because <i class="">they can always lie anyway.)</i></div><div class=""><br class=""></div><div class="">- If a library really is shipped separately from the app, enums can grow new cases, except for the ones that can't. So we need some kind of annotation here. This is your "B" in the original email, so we're all agreed here.</div><div class=""><br class=""></div><div class="">- If a library is shipped with the app, there's no chance of the enum growing a new case at run time. Does that mean we don't need a default case? (Or "unknown case" now.)</div><div class=""><br class=""></div><div class="">The answer here is most easily understood in terms of&nbsp;<a href="https://semver.org" class="">semantic versioning</a>.&nbsp;If adding a new enum case is a source-breaking change, then it's a source-breaking change, requiring a major version update. The app author decides when to update their dependencies, and might hold off on getting a newer version of a library because it's not compatible with what they have.</div><div class=""><br class=""></div><div class="">If adding a new enum case is <i class="">not</i>&nbsp;a source-breaking change, then it can be done in a minor version release of a library. Like deprecations, this can produce new warnings, but not new errors, and it should not (if done carefully) break existing code. This isn't a <i class="">critical</i>&nbsp;feature for a language to have, but I would argue (and have argued) that it's a useful one for library developers. Major releases still exist; this just makes one particular kind of change valid for minor releases as well.</div><div class=""><br class=""></div><div class="">(It also feels very subtle to me that 'switch' behaves differently based on <i class="">where the enum came from</i>. I know this whole proposal adds complexity to the language, and I'd like to keep it as consistent as possible.)</div><div class=""><br class=""></div><div class="">Okay, so what if we did this based on the 'import' rather than on how the module was compiled—Karl's `@static import`? That feels a little better to me because you can see it in your code. (Let's ignore re-exported modules for now.) But now we have two types of 'import', only one of which can be used with system libraries. That also makes me uncomfortable. (And to be fair, it's also something that can be added after the fact without disturbing the rest of the language.)</div><div class=""><br class=""></div><div class="">Finally, it's very important that whatever you do in <i class="">your</i>&nbsp;code doesn't necessarily apply to your <i class="">dependencies.</i>&nbsp;We've seen in practice that people are not willing to edit their dependencies, even to handle simple SDK changes or language syntax changes (of which there are hopefully no more). That's why I'm pushing the source compatibility aspect so hard, even for libraries that won't be shipped separately from an app.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">Overall, I think we're really trying to keep from breaking Swift into different dialects, and making this feature dependent on whether or not the library is embedded in the app would work at cross-purposes to that. Everyone would still be forced to learn about the feature if they used C enums anyway, so we're not even helping out average developers. Instead, it's better that we have one, good model for dealing with other people's enums, which in practice can and do grow new cases regardless of how they are linked.</div><div class=""><br class=""></div><div class="">Jordan</div><div class=""><br class=""></div><br class=""><div><br class=""><blockquote type="cite" class=""><div class="">On Jan 3, 2018, at 09:07, Dave DeLong &lt;<a href="mailto:swift@davedelong.com" class="">swift@davedelong.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="">IMO this is still too large of a hammer for this problem.<div class=""><br class=""></div><div class="">This whole “unexpected case” thing is only a problem when you’re linking libraries that are external to/shipped independently of&nbsp;your app. Right now, the *only* case where this might exist is Swift on the server. We *might* run in to this in the future once the ABI stabilizes and we have the Swift libraries shipping as part of iOS/macOS/Linux. Other than this, unexpected enum cases won’t really be a problem developers have to deal with.</div><div class=""><br class=""></div><div class="">Because this will be such a relatively rare problem, I feel like a syntax change like what’s being proposed is a too-massive hammer for such a small nail.</div><div class=""><br class=""></div><div class="">What feels far more appropriate is:</div><div class=""><br class=""></div><div class="">🅰️ Teaching the compiler/checker/whatever about the linking semantics of modules. For modules that are packaged inside the final built product, there is no need to deal with any unexpected cases, because we already have the exhaustiveness check appropriate for that scenario (regardless of whether the module is shipped as a binary or compiled from source). The app author decides when to update their dependencies, and updating those dependencies will produce new warnings/errors as the compiler notices new or deprecated cases. This is the current state of things and is completely orthogonal to the entire discussion.</div><div class=""><br class=""></div><div class="">and</div><div class=""><br class=""></div><div class="">🅱️ Adding an attribute (@frozen, @tangled, @moana, @whatever) that can be used to decorate an enum declaration. This attribute would only need to be consulted on enums where the compiler can determine that the module will *not* be part of the final built product. (Ie, it’s an “external” module, in my nomenclature). This, then, is a module that can update independently of the final app, and therefore there are two possible cases:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>1️⃣ If the enum is decorated with @frozen, then I, as an app author, have the assurance that the enum case will not change in future releases of the library, and I can safely switch on all known cases and not have to provide a default case.&nbsp;</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>2️⃣ If the enum is NOT decorated with @frozen, then I, as an app author, have to account for the possibility that the module may update from underneath my app, and I have to handle an unknown case. This is simple: the compiler should require me to add a “default:” case to my switch statement. This warning is produced IFF: the enum is coming from an external module, and the enum is not decorated with @frozen.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">==========</div><div class=""><br class=""></div><div class="">With this proposal, we only have one thing to consider: the spelling of @frozen/@moana/@whatever that we decorate enums in external modules with. Other than that, the existing behavior we currently have is completely capable of covering the possibilities: we just keep using a “default:” case whenever the compiler can’t guarantee that we can be exhaustive in our switching.</div><div class=""><br class=""></div><div class="">Where the real work would be is teaching the compiler about internally-vs-externally linked modules.</div><div class=""><br class=""></div><div class="">Dave<br class=""><div class=""><br class=""><blockquote type="cite" class=""><div class="">On Jan 2, 2018, at 7:07 PM, 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 class="">[Proposal:&nbsp;<a href="https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md" class="" style="font-family: Helvetica, arial, sans-serif;">https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md</a>]</div><div class=""><br class=""></div><div class="">Whew! Thanks for your feedback, everyone. On the lighter side of feedback—naming things—it seems that most people seem to like '<b class="">@frozen</b>', and that does in fact have the connotations we want it to have. I like it too.</div><div class=""><br class=""></div><div class="">More seriously, this discussion has convinced me that it's worth including what the proposal discusses as a <b class="">'future' case</b>. The key point that swayed me is that this can produce a <i class="">warning</i>&nbsp;when the switch is missing a case rather than an <i class="">error,</i>&nbsp;which both provides the necessary compiler feedback to update your code and allows your dependencies to continue compiling when you update to a newer SDK. I know people on both&nbsp;sides won't be 100% satisfied with this, but does it seem like a reasonable compromise?</div><div class=""><br class=""></div><div class="">The next question is how to spell it. I'm leaning towards `unexpected case:`, which (a) is backwards-compatible, and (b) also handles "private cases", either the fake kind that you can do in C (as described in the proposal), or some real feature we might add to Swift some day. `unknown case:` isn't bad either.</div><div class=""><br class=""></div><div class="">I too would like to just do `unknown:` or `unexpected:` but that's technically a source-breaking change:</div><div class=""><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class="">switch foo {</div><div class="">case bar:</div><div class="">&nbsp; unknown:</div><div class="">&nbsp; while baz() {</div><div class="">&nbsp; &nbsp; while garply() {</div><div class="">&nbsp; &nbsp; &nbsp; if quux() {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; break unknown</div><div class="">&nbsp; &nbsp; &nbsp; }</div><div class="">&nbsp; &nbsp; }</div><div class="">&nbsp; }</div><div class="">}</div></blockquote><div class=""><br class=""></div><div class="">Another downside of the `unexpected case:` spelling is that it doesn't work as part of a larger pattern. I don't have a good answer for that one, but perhaps it's acceptable for now.</div><div class=""><br class=""></div><div class="">I'll write up a revision of the proposal soon and make sure the core team gets my recommendation when they discuss the results of the review.</div><div class=""><br class=""></div><div class="">---</div><div class=""><br class=""></div><div class=""><div class="">I'll respond to a few of the more intricate discussions tomorrow, including the syntax of putting a new declaration inside the enum rather than outside. Thank you again, everyone, and happy new year!</div></div><div class=""><br class=""></div><div class="">Jordan</div><div class=""><br class=""></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></blockquote><blockquote type="cite"><div><span>_______________________________________________</span><br><span>swift-evolution mailing list</span><br><span><a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a></span><br><span><a href="https://lists.swift.org/mailman/listinfo/swift-evolution">https://lists.swift.org/mailman/listinfo/swift-evolution</a></span><br></div></blockquote></body></html>