Enums and Source Compatibility

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`.

------------ Begin Message ------------
Group: gmane.comp.lang.swift.evolution
MsgID: <CAGY80u=kVQA1q=5TMxXxFgM4tLGFUQh61EN1daepEMAA_FoE9Q@mail.gmail.com>

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 -------------

···

On Tue, Aug 8, 2017 at 5:27 PM, Jordan Rose via swift-evolution < swift-evolution-m3FHrko0VLzYtjvyW6yDsg@public.gmane.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.

···

On Aug 10, 2017, at 7:46 AM, James Froggatt via swift-evolution <swift-evolution@swift.org> wrote:

------------ Begin Message ------------
Group: gmane.comp.lang.swift.evolution
MsgID: <CAGY80u=kVQA1q=5TMxXxFgM4tLGFUQh61EN1daepEMAA_FoE9Q@mail.gmail.com>

On Tue, Aug 8, 2017 at 5:27 PM, Jordan Rose via swift-evolution < > swift-evolution-m3FHrko0VLzYtjvyW6yDsg@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

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)

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...
}

···

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> wrote:

------------ Begin Message ------------ Group: gmane.comp.lang.swift.evolution MsgID: <CAGY80u=kVQA1q=5TMxXxFgM4tLGFUQh61EN1daepEMAA_FoE9Q@mail.gmail.com>

On Tue, Aug 8, 2017 at 5:27 PM, Jordan Rose via swift-evolution < >> swift-evolution-m3FHrko0VLzYtjvyW6yDsg@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 https://lists.swift.org/mailman/listinfo/swift-evolution

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.

···

On Aug 10, 2017, at 9:25 AM, Vladimir.S <svabox@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:

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@mail.gmail.com>
On Tue, Aug 8, 2017 at 5:27 PM, Jordan Rose via swift-evolution < swift-evolution-m3FHrko0VLzYtjvyW6yDsg@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

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@swift.org(mailto:swift-evolution@swift.org)) schrieb:

> On Aug 10, 2017, at 9:25 AM, Vladimir.S <svabox@gmail.com(mailto:svabox@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_FoE9Q@mail.gmail.com(mailto:CAGY80u=kVQA1q=5TMxXxFgM4tLGFUQh61EN1daepEMAA_FoE9Q@mail.gmail.com)>
> > > On Tue, Aug 8, 2017 at 5:27 PM, Jordan Rose via swift-evolution < swift-evolution-m3FHrko0VLzYtjvyW6yDsg@public.gmane.org(mailto:swift-evolution-m3FHrko0VLzYtjvyW6yDsg@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

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:

Why compiler can't require 'open' enums to be public only? So, you can write but this will not compile. No?

enum Foo {
   case abc
   case def
   default
}

enum Foo {
   case abc
   default
   case def
}

enum Foo {
   default
   case abc
   case def
}

----

Why compiler can't require 'default' be declared only after last 'case' ?

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

Can't agree. Default-like 'public' for class also allows compiler to make its optimizations as it is known that there can't be a subclass of published class.

'public' for enum will in inverse block optimizations, as there is no guarantee that enum will be the same and will not change in future.

IMO the developer of external API should explicitly mark public enum as 'closed' or as 'open', to be concrete if changes in enum should/will lead to new major version of framework(so can't be changed in current version) or most likely this enum will be extended in next minor update and then "it might suddenly grow a new case containing a struct with 5000 Strings in it"(Jordan Rose)

Vladimir.

···

On 10.08.2017 18:22, Adrian Zubarev via swift-evolution wrote:

--
Adrian Zubarev
Sent with Airmail

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

On Aug 10, 2017, at 9:25 AM, Vladimir.S <svabox@gmail.com >>> <mailto:svabox@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_FoE9Q@mail.gmail.com <mailto:CAGY80u=kVQA1q=5TMxXxFgM4tLGFUQh61EN1daepEMAA_FoE9Q@mail.gmail.com>>
On Tue, Aug 8, 2017 at 5:27 PM, Jordan Rose via swift-evolution < >>>>> swift-evolution-m3FHrko0VLzYtjvyW6yDsg@public.gmane.org >>>>> <mailto:swift-evolution-m3FHrko0VLzYtjvyW6yDsg@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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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:

Why compiler can't require 'open' enums to be public only? So, you can write but this will not compile. No?

enum Foo {
  case abc
  case def
  default
}
enum Foo {
  case abc
  default
  case def
}
enum Foo {
  default
  case abc
  case def
}
----

Why compiler can't require 'default' be declared only after last 'case' ?

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

Can't agree. Default-like 'public' for class also allows compiler to make its optimizations as it is known that there can't be a subclass of published class.

'public' for enum will in inverse block optimizations, as there is no guarantee that enum will be the same and will not change in future.

Without commenting on the rest of this at the moment, this is incorrect. A 'public' class may have subclasses in the same module (public or non-public), and a 'public' class in a binary framework is also permitted to become 'open' in the future.

Jordan

···

On Aug 10, 2017, at 09:15, Vladimir.S via swift-evolution <swift-evolution@swift.org> wrote:
On 10.08.2017 18:22, Adrian Zubarev via swift-evolution wrote:

IMO the developer of external API should explicitly mark public enum as 'closed' or as 'open', to be concrete if changes in enum should/will lead to new major version of framework(so can't be changed in current version) or most likely this enum will be extended in next minor update and then "it might suddenly grow a new case containing a struct with 5000 Strings in it"(Jordan Rose)