<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=""><br class=""><div><blockquote type="cite" class=""><div class="">On 11 Feb 2017, at 20:50, Matthew Johnson via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><blockquote type="cite" class=""><div class=""><br class="Apple-interchange-newline">On Feb 11, 2017, at 12:40 PM, Xiaodi Wu <<a href="mailto:xiaodi.wu@gmail.com" class="">xiaodi.wu@gmail.com</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div dir="ltr" class="">On Sat, Feb 11, 2017 at 6:41 AM, Matthew Johnson<span class="Apple-converted-space"> </span><span dir="ltr" class=""><<a href="mailto:matthew@anandabits.com" target="_blank" class="">matthew@anandabits.com</a>></span><span class="Apple-converted-space"> </span>wrote:<br class=""><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div dir="auto" class=""><div class=""><br class=""><br class="">Sent from my iPad</div><div class=""><div class="h5"><div class=""><br class="">On Feb 10, 2017, at 9:48 PM, Xiaodi Wu <<a href="mailto:xiaodi.wu@gmail.com" target="_blank" class="">xiaodi.wu@gmail.com</a>> wrote:<br class=""><br class=""></div><blockquote type="cite" class=""><div class=""><div dir="ltr" class="">On Wed, Feb 8, 2017 at 5:05 PM, Matthew Johnson via swift-evolution<span class="Apple-converted-space"> </span><span dir="ltr" class=""><<a href="mailto:swift-evolution@swift.org" target="_blank" class="">swift-evolution@swift.org</a>></span><span class="Apple-converted-space"> </span>wrote:<br class=""><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div class="">I’ve been thinking a lot about our public access modifier story lately in the context of both protocols and enums. I believe we should move further in the direction we took when introducing the `open` keyword. I have identified what I think is a promising direction and am interested in feedback from the community. If community feedback is positive I will flesh this out into a more complete proposal draft.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">Background and Motivation:</div><div class=""><br class=""></div>In Swift 3 we had an extended debate regarding whether or not to allow inheritance of public classes by default or to require an annotation for classes that could be subclassed outside the module. The decision we reached was to avoid having a default at all, and instead make `open` an access modifier. The result is library authors are required to consider the behavior they wish for each class. Both behaviors are equally convenient (neither is penalized by requiring an additional boilerplate-y annotation).<div class=""><br class=""></div><div class="">A recent thread (<a href="https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031566.html" target="_blank" class="">https://lists.swift.org/piper<wbr class="">mail/swift-evolution/Week-of-M<wbr class="">on-20170206/031566.html</a>) discussed a similar tradeoff regarding whether public enums should commit to a fixed set of cases by default or not. The current behavior is that they *do* commit to a fixed set of cases and there is no option (afaik) to modify that behavior. The Library Evolution document (<a href="https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums" target="_blank" class="">https://github.com/apple/swif<wbr class="">t/blob/master/docs/LibraryEvol<wbr class="">ution.rst#enums</a>) suggests a desire to change this before locking down ABI such that public enums *do not* make this commitment by default, and are required to opt-in to this behavior using an `@closed` annotation.</div><div class=""><br class=""></div><div class="">In the previous discussion I stated a strong preference that closed enums *not* be penalized with an additional annotation. This is because I feel pretty strongly that it is a design smell to: 1) expose cases publicly if consumers of the API are not expected to switch on them and 2) require users to handle unknown future cases if they are likely to switch over the cases in correct use of the API.</div><div class=""><br class=""></div><div class="">The conclusion I came to in that thread is that we should adopt the same strategy as we did with classes: there should not be a default.</div><div class=""><br class=""></div><div class="">There have also been several discussions both on the list and via Twitter regarding whether or not we should allow closed protocols. In a recent Twitter discussion Joe Groff suggested that we don’t need them because we should use an enum when there is a fixed set of conforming types. There are at least two reasons why I still think we *should* add support for closed protocols.</div><div class=""><br class=""></div><div class="">As noted above (and in the previous thread in more detail), if the set of types (cases) isn’t intended to be fixed (i.e. the library may add new types in the future) an enum is likely not a good choice. Using a closed protocol discourages the user from switching and prevents the user from adding conformances that are not desired.</div><div class=""><br class=""></div><div class="">Another use case supported by closed protocols is a design where users are not allowed to conform directly to a protocol, but instead are required to conform to one of several protocols which refine the closed protocol. Enums are not a substitute for this use case. The only option is to resort to documentation and runtime checks.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">Proposal:</div><div class=""><br class=""></div><div class="">This proposal introduces the new access modifier `closed` as well as clarifying the meaning of `public` and expanding the use of `open`. This provides consistent capabilities and semantics across enums, classes and protocols.</div><div class=""><br class=""></div><div class="">`open` is the most permissive modifier. The symbol is visible outside the module and both users and future versions of the library are allowed to add new cases, subclasses or conformances. (Note: this proposal does not introduce user-extensible `open` enums, but provides the syntax that would be used if they are added to the language)</div><div class=""><br class=""></div><div class="">`public` makes the symbol visible without allowing the user to add new cases, subclasses or conformances. The library reserves the right to add new cases, subclasses or conformances in a future version.</div><div class=""><br class=""></div><div class="">`closed` is the most restrictive modifier. The symbol is visible publicly with the commitment that future versions of the library are *also* prohibited from adding new cases, subclasses or conformances. Additionally, all cases, subclasses or conformances must be visible outside the module.</div><div class=""><br class=""></div><div class="">Note: the `closed` modifier only applies to *direct* subclasses or conformances. A subclass of a `closed` class need not be `closed`, in fact it may be `open` if the design of the library requires that. A class that conforms to a `closed` protocol also need not be `closed`. It may also be `open`. Finally, a protocol that refines a `closed` protocol need not be `closed`. It may also be `open`.</div><div class=""><br class=""></div><div class="">This proposal is consistent with the principle that libraries should opt-in to all public API contracts without taking a position on what that contract should be. It does this in a way that offers semantically consistent choices for API contract across classes, enums and protocols. The result is that the language allows us to choose the best tool for the job without restricting the designs we might consider because some kinds of types are limited with respect to the `open`, `public` and `closed` semantics a design might require.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">Source compatibility:</div><div class=""><br class=""></div><div class="">This proposal affects both public enums and public protocols. The current behavior of enums is equivalent to a `closed` enum under this proposal and the current behavior of protocols is equivalent to an `open` protocol under this proposal. Both changes allow for a simple mechanical migration, but that may not be sufficient given the source compatibility promise made for Swift 4. We may need to identify a multi-release strategy for adopting this proposal.</div><div class=""><br class=""></div><div class="">Brent Royal-Gordon suggested such a strategy in a discussion regarding closed protocols on Twitter:</div><div class=""><br class=""></div><div class="">* In Swift 4: all unannotated public protocols receive a warning, possibly with a fix-it to change the annotation to `open`.</div><div class="">* Also in Swift 4: an annotation is introduced to opt-in to the new `public` behavior. Brent suggested `@closed`, but as this proposal distinguishes `public` and `closed` we would need to identify something else. I will use `@annotation` as a placeholder.</div><div class="">* Also In Swift 4: the `closed` modifier is introduced.</div><div class=""><br class=""></div><div class="">* In Swift 5 the warning becomes a compiler error. `public protocol` is not allowed. Users must use `@annotation public protocol`.</div><div class="">* In Swift 6 `public protocol` is allowed again, now with the new semantics. `@annotation public protocol` is also allowed, now with a warning and a fix-it to remove the warning.</div><div class="">* In Swift 7 `@annotation public protocol` is no longer allowed.</div><div class=""><br class=""></div><div class="">A similar mult-release strategy would work for migrating public enums.</div></div></blockquote><div class=""><br class=""></div><div class="">A different line of feedback here:</div><div class=""><br class=""></div><div class="">As per previous reply, I now think if we clarify the mental model of the access modifier hierarchy you're proposing and adopt or reject with that clarity, we'll be fine whether we go with `closed` or with `@closed`. But I don't think the source compatibility strategy you list is the most simple or the most easy to understand for end users.</div></div></div></div></div></blockquote><div class=""><br class=""></div></div></div>I'm pretty neutral on what kind of source compatibility strategy we would adopt. I am happy to defer to the community and core team.<div class=""><span class=""><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><div class=""><br class=""></div><div class="">- I'll leave aside closed protocols, which as per Jordan Rose's feedback may or may not have sufficient interestingness.</div></div></div></div></div></blockquote><div class=""><br class=""></div></span><div class="">Jordan supported allowing protocols to have the same choice of contract that classes do today. `public protocol` has the same meaning as `open class` today so if we want consistency we need a breaking change.</div></div></div></blockquote><div class=""><br class=""></div><div class="">Sure; I was specifically considering the phased introduction of `closed`. It's been a while since I've thought about how to phase in a change regarding public protocols and open protocols.</div><div class=""><br class=""></div><div class="">That said, others make good points about _conforming to_ protocols by a type vs. _refining_ protocols by another protocol, and whether either or both of these is more akin to subclassing a class.</div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">This is something that was in the back of my mind for months (I’ve thought about this off and on since last summer). My conclusion is that *conforming* is the important relationship, at least in terms of the `open`, and `closed` discussion. </div><div class=""><br class=""></div><div class="">As I mentioned in my reply to Karl, I can’t think of any benefit that would be afforded to either a library or its clients by restricting refinement. Obviously clients get more flexibility if they *can* refine protocols defined by a library. From the perspective of a library author nothing changes if a client refines a protocol it defines. All of the semantics of the code in the library is identical either way, as is it’s options for future evolution.</div></div></div></blockquote><div><br class=""></div>Yeah that’s fine, I was just saying that any proposal should make it clear that those sub-protocols are closed externally (I don’t really like the term “refinements” — those sub-protocols may add new requirements). Perhaps we should require an explicit “closed” annotation when deriving a sub-protocol from a closed protocol in another module?</div><div><br class=""></div><div><blockquote type="cite" class=""><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><div class=""><br class=""></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div dir="auto" class=""><div class=""><span class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><div class="">- With respect to enums, I don't think we need such a drastic whiplash in terms of what will compile in future versions. Instead, we could take a more pragmatic approach:</div><div class=""><br class=""></div><div class="">1. In Swift 4, remove the warning (or is it error?) about `default` cases in switch statements over public enums. Simultaneously, add `closed` or `@closed` (whatever is the approved spelling) and start annotating standard library APIs. The annotation will be purely future-proofing and have no functional effect (i.e. the compiler will do nothing differently for a `closed enum` or `@closed public enum` (as the case may be) versus a plain `public enum`).<br class=""></div><div class="">2. In Swift 4.1, _warn_ if switch statements over public enums don't have a `default` statement: offer a fix-it to insert `default: fatalError()` and, if the enum is in the same project, offer a fix-it to insert `closed` or `@closed`.</div></div></div></div></div></blockquote><div class=""><br class=""></div></span><div class="">Why do you say "if the enum is in the same project, offer a fix-it to insert `closed`? If the enum is in the same project we can perform an exhaustive switch regardless of its public API contract (except for `open` enums if we decide to add those).</div></div></div></blockquote><div class=""><br class=""></div><div class="">Hmm, well now I'm not in favor of my own suggestion. A public enum, though it may gain or lose cases in future versions, can be exhaustively switched over in the present whether it's same-module or third-party. No warning or error should issue on attempting to switch over a public enum without a default case.</div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">This is true for the current semantics of `public enum`. But what I am suggesting is that this semantic be called `closed enum`. `public enum` would allow libraries to add new cases resiliently. This is the same semantic for `public enum` that is mentioned in the Library Evolution document (which spells my `closed enum` as `@closed public enum`). </div><div class=""><br class=""></div><div class="">We have to require a default case for resilient enums because the client may run against a future version of the library with a new case. I think a couple people have mentioned either allowing an implicit default case with `break` or `fatalError` to be synthesized but I am strongly opposed to this. The only other option is a compiler error for a switch over a resilient enum that does not have a default clause.</div></div></div></blockquote><div><br class=""></div><div>The Library Evolution document isn’t gospel; it’s more like a collection of musings and aspirations, so I don’t think we should be bound by the syntax it uses.</div><div><br class=""></div><div>WRT your point earlier that enums are not suitable for a mutually-exclusive set of options and that structs should be used instead - I think you have it backwards. That is precisely what enums *are* suitable for. For example, FloatingPointSign and FloatingPointRoundingMode in the standard library. If you want to represent something looser - say an integer which _could_ take any value, but has a few particular salient values, that would best be expressed in a wrapper struct with an embedded enum for the salient values.</div><div><br class=""></div><div>I don’t believe that forwards-compatible enums are that vital to the conceptual model that they should be the default, in fact I think they are very conceptually different to how enums are used today and what they were designed for. For example, the compiler squashes the layout of an enum in to the smallest type which can represent all of its cases - so if you only have 2 cases, your enum will be an Int1 (possibly stored in a way which overlaps the payload, if there are spare bits to do so). Making your enum raw-representable as an Int32 or whatever doesn’t actually have any effect on this - an enum of 100 Int32-valued cases will get represented as an Int8; they were not designed to carry arbitrary numbers of additional cases. Conceptually, too, enums are easy to reason about because they can only have one out of a set of known values -- knowing that an objects ’state’ variable is one of { notStarted, running, finished } is what separates an enum from an actual Integer. The truth is there is only so many changes you can make to an enum before you invalidate the reasoning of client code.</div><div><br class=""></div><div>As for backwards-compatibility, as I said before, I believe that is best done with versioning. So library authors can add/remove cases, and application developers can follow along while supporting systems that are stuck on older library versions (e.g. Foundation).</div><div><br class=""></div><div>If we introduce some kind of forwards-compatible enum, it should explicitly be something different (like an “open” enum). It’s the nichest of niche-cases, would change the layout and prohibit optimisations, and make those objects more difficult to reason about.</div><br class=""><blockquote type="cite" class=""><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><div class=""> </div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div dir="auto" class=""><div class=""><span class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><div class="">3. In Swift 5, upgrade the warning to an error for non-exhaustiveness if a switch statement over a public enum doesn't have a `default` statement. Now, new syntax to extend an `open enum` can be introduced and the compiler can treat closed and public enums differently.</div></div></div></div></div></blockquote><br class=""></span></div><div class="">If the community and core team support this strategy I will also. It seems reasonable and speeds up the transition by using the point release. That's a great idea!</div></div></blockquote></div><br class=""></div></div></div></blockquote></div><br class="" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class="">_______________________________________________</span><br style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class="">swift-evolution mailing list</span><br style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a></span><br style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class=""><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" class="">https://lists.swift.org/mailman/listinfo/swift-evolution</a></span></div></blockquote></div><br class=""></body></html>