[swift-evolution] Enums and Source Compatibility

Adrian Zubarev adrian.zubarev at devandartist.com
Thu Aug 10 10:07:24 CDT 2017


I think this design does not avoid you writing something like `private enum Foo { default ... }`, which is redudant as Jordan already pointed out in his previous post, nor does it have a consistent way of declaration:  

enum Foo {  
 case abc
 case def
 default
}

enum Foo {  
 case abc
 default
 case def
}


enum Foo {  
 default
 case abc
 case def
}

----  

On the other hand I'd be very much in favor of the design David has pitched, which makes `public` as a soft default for public API's and adds a stronger constraints with an extra keyword OR a different access modifier. In case of an access modifier it would be really interesting if someone knows other use cases where we could reuse `closed` for similar purposes.  

Long story short:  
- public is the soft default and all uses of an enum from module A in module B would require `default` in swithch statements
- closed is the stronger implied `public` which makes the enum finite and does not require `default` if you switch on all cases
--  
Adrian Zubarev
Sent with Airmail  

Am 10. August 2017 um 16:55:00, Matthew Johnson via swift-evolution (swift-evolution at swift.org(mailto:swift-evolution at swift.org)) schrieb:

>  
>  
> > On Aug 10, 2017, at 9:25 AM, Vladimir.S <svabox at gmail.com(mailto:svabox at 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 at swift.org(mailto:swift-evolution at 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_FoE9Q at mail.gmail.com(mailto:CAGY80u=kVQA1q=5TMxXxFgM4tLGFUQh61EN1daepEMAA_FoE9Q at mail.gmail.com)>
> > > > On Tue, Aug 8, 2017 at 5:27 PM, Jordan Rose via swift-evolution < swift-evolution-m3FHrko0VLzYtjvyW6yDsg at public.gmane.org(mailto:swift-evolution-m3FHrko0VLzYtjvyW6yDsg at 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170810/feedd45d/attachment.html>


More information about the swift-evolution mailing list