> On Aug 10, 2017, at 9:25 AM, Vladimir.S <sva...@gmail.com> wrote: > > On 10.08.2017 16:46, Matthew Johnson via swift-evolution wrote: >>> On Aug 10, 2017, at 7:46 AM, James Froggatt via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>> Since it seems to have been lost in the noise, I want to second with >>> support for >>> Xiaodi's syntax of having `default` appearing in the enum declaration >>> itself. >>> It's much clearer in its intention, feels very ‘Swifty’, and more >>> importantly it >>> doesn't prompt whole threads debating the semantics of `open` vs `public`. >> I think Xiaodi’s syntax is very elegant if we want to avoid the access >> control >> style syntax. However, it does one problem: the “error of omission” (not >> thinking >> about open vs closed) leaves a library author with a closed enum, preventing >> them >> from adding cases in the future without breaking compatibility. I’m not >> sure this >> is acceptable. > > Then, doesn't this mean that any 'usual' enum should be 'open' by default, > and only enum declared with some marker (like 'final' or 'enum(sealed)') can > be 'closed'? > > Otherwise we need to require an explicit marker for *each* enum, and so break > the source compatibility? (we'll have to append that marker to each enum in > your current code)
This is a good point. A good first decision is whether we prioritize source compatibility or the more conservative “default”. If source compatibility is prioritized Xiaodi’s proposed syntax is probably hard to beat. > > Also I'd suggest this for closed enum: > > enum MyClosedEnum { > case a > case b > case c > final > } > > So, for public closed enum it will looks like: > > public enum MyClosedEnum { > case a > case b > case c > final > } > > Also, if we need to explicitly mark open enum, probably we can consider > 'continue' keyword, as IMO is not clear what 'default' is saying on > declaration site('you must insert `default` in switch'? 'there are other > `default` cases'?) : > > public enum MyOpenEnum { > case a > case b > case c > continue // to be continue... > } > > >>> ------------ Begin Message ------------ Group: >>> gmane.comp.lang.swift.evolution MsgID: >>> <CAGY80u=kVQA1q=5tmxxxfgm4tlgfuqh61en1daepemaa_fo...@mail.gmail.com> >>> On Tue, Aug 8, 2017 at 5:27 PM, Jordan Rose via swift-evolution < >>> swift-evolution-m3fhrko0vlzytjvyw6y...@public.gmane.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 >>> Jordan, I'm glad you're bringing this back up. I think it's clear that >>> there's >>> appetite for some forward movement in this area. >>> With respect to syntax--which the conversation in this thread has tackled >>> first--I agree with the discussion that "open" and "closed" are attractive >>> but >>> also potentially confusing. As discussed in earlier threads, both "open" and >>> "closed" will constrain the enum author and/or user in ways above and beyond >>> "public" currently does, but the terminology does not necessarily reflect >>> that >>> (as open is the antonym of closed); moreover, the implications of using >>> these >>> keywords with enums don't necessarily parallel the implications of using >>> them >>> with classes (for example, an open class can be subclassed; an open enum >>> that >>> gains additional cases is, if anything, something of a supertype of the >>> original). >>> I'd like to suggest a different direction for syntax; I'm putting it forward >>> because I think the spelling itself naturally suggests a design as to which >>> enums are (as you call it) "open" or "closed," and how to migrate existing >>> enums: >>> ``` enum MyClosedEnum { case a case b case c } >>> enum MyOpenEnum { case a case b case c default } ``` >>> In words, an enum that may have future cases will "leave room" for them by >>> using >>> the keyword `default`, sort of paralleling its use in a switch statement. >>> All >>> existing Swift enums can therefore continue to be switched over >>> exhaustively; >>> that is, this would be an additive, source-compatible change. For >>> simplicity, we >>> can leave the rules consistent for non-public and public enums; or, we could >>> prohibit non-public enums from using the keyword `default` in the manner >>> shown >>> above. Obj-C enums would be imported as though they declare `default` unless >>> some attribute like `enum_extensibility` is used to annotate them. >>> Thoughts? >>> ------------- End Message ------------- >>> _______________________________________________ swift-evolution mailing >>> list swift-evolution@swift.org >>> https://lists.swift.org/mailman/listinfo/swift-evolution >> _______________________________________________ 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