[swift-evolution] [Pitch] consistent public access modifiers

Nevin Brackett-Rozinsky nevin.brackettrozinsky at gmail.com
Sun Feb 12 10:39:32 CST 2017

Alternative: leave “public enum” as it is now, and spell the resilient
version “@resilient enum”


On Sunday, February 12, 2017, Matthew Johnson via swift-evolution <
swift-evolution at swift.org> wrote:

> On Feb 12, 2017, at 10:24 AM, David Hart <david at hartbit.com
> <javascript:_e(%7B%7D,'cvml','david at hartbit.com');>> wrote:
> On 12 Feb 2017, at 16:38, Matthew Johnson <matthew at anandabits.com
> <javascript:_e(%7B%7D,'cvml','matthew at anandabits.com');>> wrote:
> On Feb 12, 2017, at 12:50 AM, David Hart <david at hartbit.com
> <javascript:_e(%7B%7D,'cvml','david at hartbit.com');>> wrote:
> Hi Matthew,
> I've read your proposal ideas and most of the discussions on the thread,
> and I'd like to provide some personal feedback.
> Swift already has a complicated "access modifier" story so I think we
> really want a good reason to introduce a new one. And the problem I see is
> that `closed` has much less semantic weight than the other modifiers.
> How so?  I’m not sure if I catch your meaning here.  It feels to me like
> it has the same semantic weight as `open`: prohibiting future versions of a
> module from adding cases / subclasses / conformances is roughly the inverse
> of lifting the restriction that clients cannot add those things.  Therefore
> it has roughly the same degree of additional meaning over `public` as
> `open` does.
> The difference I see is precisely that 'public' and 'open' modifiers limit
> what the client of a module can do while closed limits what future versions
> of a module can do. Feels quite different to me.
> This is a reasonable point and is perhaps the strongest argument made
> against my proposal thus far.  However, I think we have to consider my
> proposal relative to the alternatives.
> The only alternative I am aware of is making `public enum` the resilient
> variety and using `@closed public enum` for the closed variety.  This means
> that `public` will have at least two different semantics (three if we don’t
> reconcile classes and protocols).  It also means that the resilient variety
> is effectively the default.  I am really happy that we decide not to have a
> default between `open` and `public` and think the best choice is that we
> don’t have one here either.  The fact that we have a way to do this while
> solving the inconsistent semantics of `public` feels like a net win to me.
> First of all, the Library Evolution document you linked says toward at the
> top that "this document is primarily concerned with binary compatibility,
> i.e. what changes can safely be made to a library between releases that
> will not break memory-safety or type-safety, or cause clients to fail to
> run at all." It seems to me that the @closed introduced in that document is
> much more about library resilience than about only closing down the
> addition of new cases: that's why it also talks about reordering and all
> other changes that can change the memory layout.
> Swift 3 having introduced both fileprivate and open has complexified the
> access level story for developers and library authors. That complexity is
> the cost that we have paid for more expressiveness. But if we continue
> adding new access control modifiers to express new semantics, we may be
> going too far: perfect is the enemy of good.
> Both of those arguments explain why I think closed should be introduced,
> but only as a rarely-used attribute for library authors which need to
> express ABI resilience, and not as an extra access modifier.
> `closed` is about much more than binary compatibility.  Any time a library
> publishes an enum that clients can reasonably be expected to switch
> statements over the library should strive to make it `closed` wherever
> possible.  Otherwise clients are expected to handle unknown future cases by
> design.  That is a design smell if you ask me.  This means that we can
> expect libraries to often carefully design such enums in a way that allows
> them to be `closed`.  The use case for resilient enums is in things like
> mutually exclusive option sets received as input to the module and for
> which it would be unusual for clients of the library to write a switch
> statement over.
> With this in mind, `closed` should not be a rarely-used attribute at all.
> In fact it will often be the best choice.  This is a big motivation behind
> my desire to see it on equal footing with `public` and `open`.
> In regards to the complexity of the access model - if you look closely,
> `public` has three subtly different meanings today.  That kind of
> inconsistency is part of the complexity of it.  And as noted, `closed` is a
> concept that *will* play a significant role in Swift, regardless of how we
> spell it.  What my proposal aims to do is to incorporate it into a
> consistent system of outside-the-module access modifiers.
> One can make a very reasonable argument that access modifiers should
> *only* be in the business of talking about visibility and should stay out
> of the business of talking about “who can add to the set of cases /
> subclasses / conformances”.  The time for that argument was when we had the
> `open` discussion last year.  I happen to like the direction we went
> because it places `public` and `open` on equal footing.  And now that we
> *have* decided to go in this direction, I think we should stick with it
> when we introduce `closed`.
> David
> On 9 Feb 2017, at 00:05, Matthew Johnson via swift-evolution <
> swift-evolution at swift.org
> <javascript:_e(%7B%7D,'cvml','swift-evolution at swift.org');>> wrote:
> 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.
> Background and Motivation:
> 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).
> A recent thread (https://lists.swift.org/pipermail/swift-evolution/
> Week-of-Mon-20170206/031566.html) 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 (https://github.com/apple/swift/blob/master/docs/
> LibraryEvolution.rst#enums) 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.
> 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.
> 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.
> 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.
> 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.
> 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.
> Proposal:
> 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.
> `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)
> `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.
> `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.
> 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`.
> 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.
> Source compatibility:
> 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.
> Brent Royal-Gordon suggested such a strategy in a discussion regarding
> closed protocols on Twitter:
> * In Swift 4: all unannotated public protocols receive a warning, possibly
> with a fix-it to change the annotation to `open`.
> * 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.
> * Also In Swift 4: the `closed` modifier is introduced.
> * In Swift 5 the warning becomes a compiler error.  `public protocol` is
> not allowed.  Users must use `@annotation public protocol`.
> * 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.
> * In Swift 7 `@annotation public protocol` is no longer allowed.
> A similar mult-release strategy would work for migrating public enums.
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> <javascript:_e(%7B%7D,'cvml','swift-evolution at swift.org');>
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170212/04b67f81/attachment-0001.html>

More information about the swift-evolution mailing list