[swift-evolution] [Draft] A Consistent Foundation For Access Control: Scope-Bounded Capabilities

Daniel Leping daniel at crossroadlabs.xyz
Fri Mar 3 05:23:10 CST 2017

General impression is positive in case shortcuts (private, bulblic) still
remain for the most common use cases.

On Thu, 2 Mar 2017 at 21:58 Matthew Johnson via swift-evolution <
swift-evolution at swift.org> wrote:

> I’ve been continuing to think about how to provide clear and consistent
> semantics for access control in Swift.  This draft represents what I think
> is the best way to accomplish that.  It eliminates the current
> inconsistencies and establishes a principled basis for the features we have
> today as well as the enhancements we may need in the future.  It does this
> with minimal breaking changes.
> The draft is included below and can also be found here:
> https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md
> .
> I’m looking forward to everyone’s feedback.
> Matthew
> A Consistent Foundation For Access Control: Scope-Bounded Capabilities
>    - Proposal: SE-NNNN
>    <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md>
>    - Authors: Matthew Johnson <https://github.com/anandabits>
>    - Review Manager: TBD
>    - Status: Awaiting review
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#introduction>
> Introduction
> This proposal introduces a consistent foundation for all access control in
> Swift: scope-bounded capabilities. The existing access control features are
> generalized with a single mechanism that unifies their semantics. This
> unified mechanism eliminates the inessential complexity and inconsistency
> of the current system while expanding its utility.
> Swift-evolution thread: Discussion thread topic for that proposal
> <https://lists.swift.org/pipermail/swift-evolution/>
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#motivation>
> Motivation
> The new access control features in Swift 3 have proven to be extremely
> controversial. The most common refrain is that we need a more simple
> system. In order to accomplish this we need to do more than tweak the
> system we already have. We need to revisit the foundation of the system
> itself.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#simple-made-easy>Simple
> Made Easy
> Rich Hickey gave a fantastic talk called [Simple Made Easy])(
> https://www.infoq.com/presentations/Simple-Made-Easy). In this talk Rich
> explores the etymology and relationship of the words "simple", "complex",
> and "easy". The meanings he explores are:
>    - Complex: entangled, intertwined, interleaved, braided together
>    - Simple: one strand, single focus, disentangled
>    - Easy: familiar, nearby, readily at hand
> The central point Rich makes in this talk is that when a design entangles
> two orthogonal concepts complexity is the result. He coins the term
> "complect" to refer to this kind of inessential complexity. This complexity
> can be removed by disentangling the concepts. Instead of "complecting"
> independent concerns we can *compose* them.
> The result is a simpler system. It is simpler because independent concepts
> can be considered and understood independently of each other.
> The composition of independent concerns also results in a more flexible
> system. When orthogonal concepts are entangled it is more difficult to
> extend the system to meet future needs. One concept cannot be extended
> independently of the other. It is not possible to make independent
> decisions about what should be orthogonal aspects of the design.
> Rich believes that the programming community is often too quick to reach
> for an immediately "easy" solution. Unfortunately, the "easy" solution
> often entangles concepts and are therefor actually complex. He suggests
> that we *first*design a simple (i.e. disentangled) solution and then
> layer ease of use and familiarity on top, thus the title "Simple Made Easy".
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#two-orthogonal-concepts>Two
> orthogonal concepts
> The access control system in Swift 3 incorporates two orthogonal concepts:
> availability and capability. Availability is what immediately comes to mind
> when one thinks of access control: a symbol is either available or it is
> not. Capability is more nuanced. It refers to what you can *do* with that
> symbol.
> Each declaration supports a *basic* capability which is always available
> when the symbol itself is available. Many declarations also offer
> *additional* capabiities (such as the ability to inherit, override, set a
> property, etc). These additional capabilities may be *less available* than
> the symbol itself.
> In Swift, availability is always specified in terms of a scope. Swift does
> not currently have a consistent way to talk about capabilities. Thus far we
> have introduced new syntax every time we wish to distinguish the
> availabiltiy of an *additional*capability from that of the symbol itself:
>    - open vs public access modifiers classes and methods
>    - Access modifier parameterization for setter availability:
>    private(set)
>    - The @closed attribute which has been discussed as a way to specify
>    non-resilient enums in Swift 4*
> It is clear that we need to be able to talk about not just basic
> availability, but also *capabilities*. It would be very nice if we had one consistent
> way to do this. This can be accomplished by composing the concepts of
> availability and capability into the notion of a *scope-bounded
> capability*.
> *@closed would lie outside the access control system proper. It is
> included for the sake of completeness. It is also included to demonstrate
> how the language currently lacks a clear and obvious way to specify new
> capability bounds when they are arise.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#problems-with-swifts-access-control-system>Problems
> with Swift's access control system
> Swift's current access control system can has several problems.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#inconsistency>
> Inconsistency
> As noted above, the ways *additional* capabilities are bounded is
> inconsistent. The semantics of public are also inconsistent.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#internal-default>Internal
> default
> The Swift evolution community has adopted the principle that nothing
> should be available outside a module without an explicit declaration of
> intent by a library author. This is an excellent default which protects
> library authors against making an error of omission that would require a
> breaking change to correct. Unfortunately this principle has not been
> consistently applied.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#public>
> public
> In *most* cases public only provides access to the *basic* capability a
> declaration offers. This is true by definition for declarations do not
> offer *additional* capabilities but it is *also* true for classes (with
> respect to inheritance) and class methods (with respect to overrides).
> However, there are three cases where public currently provides access to
> *additional* capabilities:
>    - public var allows access to the setter
>    - public enum allows exhaustive switch
>    - public protocol allows new conformances to be introduced
> It is not currently possible to declare resilient enums or closed
> protocols but both have received significant discussion. Further, resilient
> enums need to be supported before ABI stability is declared. A consistent
> access control system would treat these as independent capabilities that
> are not made available with a simple public declaration.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#private-and-fileprivate>
> private and fileprivate
> The most heavily debated aspect of the changes to the access control
> system in Swift 3 is without question the change in meaning of private to
> be the current lexical scope and the renaming of the file-level scope to
> fileprivate. This change was made with the idea that a lexically scoped
> private would prove to be a good "soft default" for a less-than-module
> availability bound. While many users appreciate the semantics of a
> scope-based access modifier it has not proven to be a good "soft default"
> and therefore does not deserve the name private.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions>
> Extensions
> In languages without extensions lexically scoped availability is
> equivalent to type-based availability for members of a type. In such a
> language it could make a reasonable default. Swift is not such a language.
> Using several extensions on the same type within the same file is an
> extremely common Swift idiom. This idiom is not well supported by a "soft
> default" of scope-based availability. The tension between a pervasive idiom
> and the "soft default" leads to confusion about when scope-based a
> availability is appropriate, and often an overuse of the feature. It also
> leads to the need to use fileprivate much more frequently than is
> desirable for such an awkward keyword.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types-and-members>Types
> and members
> A "soft default" should not have subtle behavior that has the potential to
> confuse beginners. Most beginners would expect Foo and bar in the
> following example to have the same visibility. This was true in Swift 2 but
> it is not true in Swift 3.
> private struct Foo {
>     private var bar = 42
> }
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#an-advanced-feature>An
> advanced feature
> Lexically scoped availability has important uses such as preserving
> invariants. All access to invariant-related state can be routed through
> basis methods which access the state carefully without violating
> invariants, even when that access happens in an extension in the same file.
> We should not abandon this tool but it should not be the "soft default". It
> is best reserved for specific use cases where the guarantee it offers is
> important to correctess of the software.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#essential-and-inessential-complexity>Essential
> and inessential complexity
> The inconsistencies noted above and a bad "soft default" of private are
> all forms of *inessential* complexity. This makes Swift's access control
> system more difficult to understand and use than it needs to be and causes
> confusion.
> At the same time the *essential* complexity of capabilities that are
> bounded independent of basic symbol availability is not explicitly
> acknowledged and embraced. This also makes the access control system more
> difficult to understand and use than it should be. Users are not taught to
> think in terms of independently bounded capabilities. This is a concept
> that *could* be learned once and applied generally if it was more visible
> in the language.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#proposed-solution>Proposed
> solution
> The proposed solution is to establish a semantic foundation for access
> control that is *simple* in the sense of composing rather than
> interleaving independent concerns. The solution is made *easy* by
> defining familiar names in terms of this foundation while preserving the
> semantics Swift users expect them to have. It is *consistent* in its use
> of a single mechanism for bounding capabilities and its default of
> internal for *all* capabilities.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scope-bounded-capabilities>Scope-bounded
> capabilities
> All access control is defined in terms of a parameterized access modifier
> that allows the user to specify a capability and a scope that bounds that
> capability.
> // The scope of the setter is the current file.scope(set, file) var foo = 42
> Here I suggest a bit different API to make it very distinct on which
capability gets which access. Instead of ',' use ':'

scope(set: file) vat foo = 42

Each parameter has a default argument. The default argument for the
> capability is simply the *basic* capability the declaration provides. For
> a variable this is the getter, for a method it is the ability to call the
> method, for a type it is the ability to use the type and so on. The default
> argument for the scope is the current lexical scope.
> // The scope of the getter is the current lexical scope.// This is equivalent to `private var foo = 42` in Swift 3.scope var foo = 42
> // The scope of the getter is the current file.scope(file) var bar = 42
> // The scope of the setter is the current lexical scope.scope(set) var baz = 42
> The scope of the basic capability implicitly bounds additional
> capabilities: if basic use of a symbol is not available it is not possible
> to anything with that symbol. This is similar to the existing rule that a
> type implicitly bounds the availability of all symbols declared within its
> scope: a public property of an internal type is not available outside the
> module because the type itself is not available.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#aliases>
> Aliases
> This modifier is simple (in the sense defined above), general and
> powerful. However it is also unfamiliar, often slightly verbose, and offers
> a very wide configuration space. Familiar aliases are provided as "soft
> defaults" which are recommended for common use cases.
> These aliases introduce no additional semantics. Once a user understand
> scopes, capabilities and how they compose to produce scope-bounded
> capabilities the user also has the ability to understand *all* aliases we
> introduce. Tools could even make the definition of the alias available to
> remind the user of its underlying meaning (similarly to they way Xcode
> allows a user to command-click to see a symbol definition).
> These aliases are defined in terms of the parameterized scope modifier:
>    - private(capability) = scope(capability, file)
>    - internal(capability) = scope(capability, submodule)
>    - public(capability) = scope(capability, everywhere)
>    - open = scope(inherit, everywhere)
>    - open = scope(override, everywhere)
>    - final = scope(inherit, nowhere)
>    - final = scope(override, nowhere)
>    - total = scope(switch, everywhere)
> private reverts to the Swift 2 semantics.  scope with no parameters has
> semantics equivalent to that of private in Swift 3.
> internal is specified in terms of submodule and is equivalent to module scope
> until submodules are introduced. It is specified this way to indicate the
> intent of the author should submodules be added.
> total is a placholder subject to bikeshedding.  total enum provides
> semantics equivalent to public enum.  public enum receives the semantics
> of resilient enums. If a suitable shorthand is not identified the slightly
> longer public(switch) enum can be used to specify an enum which supports
> exhaustive switch outside the module.
> open, final and closed are overloaded based on the kind of declaration
> they are applied to.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scopes>
> Scopes
> The hierarchy of scopes is as follows:
>    - nowhere
>    - lexical
>    - TypeName
>    - extension
>    - file
>    - submodule
>    - SubmoduleName
>    - module
>    - everywhere
> The name of any ancestor type or submodule of a declaration, including the
> immediately containing type or submodule, form the set of valid
> user-defined scope references.
> Including nowhere allows us to define final in terms of this system. It
> also allows us to model all properties and functions with the same set of
> capabilities: the setter of a read only property is automatically bounded
> to nowhere and the override capability of a function that is not a class
> method is automatically bounded to nowhere.
> Allowing users to reference any ancestor scope introduces affords advanced
> users a degree of control that is not possible in the current access
> control system. If submodules are introduced into Swift this additional
> control will be especially useful as a means to facilitate bounded
> collaboration between peer submodules allowing them to communicate in ways
> not available to the rest of the module.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#capabilities>
> Capabilities
> The capabilities available depend on the kind of declaration an access
> modifier is applied to. All declarations offer a *basic*capability that
> is always available when the declaration itself is available. The basic
> capability is specified by default when the scope modifier or a
> parameterized alias is used without an explicit capability argument. Some
> declarations also offer *additional* capabilities which may have an
> independent bound applied to them.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#properties-and-subscripts>Properties
> and subscripts
>    - get (the basic capability)
>    - set (for readwrite properties only)
>    - override (for class properties only)
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#functions-and-initializers>Functions
> and initializers
>    - call (the basic capability)
>    - override (for class methods only)
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#types>
> Types
>    - use (the basic capability)
>    - inherit (for classes)
>    - switch (for enums)
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#protocols>
> Protocols
>    - use (the basic capability)
>    - conform
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#extensions-and-typealiases>Extensions
> and typealiases
>    - use (the basic capability)
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#scalable-in-the-future>Scalable
> in the future
> As the language grows the mechanism of scope-bounded capabilities can be
> extended in an obvious way to meet the needs of future declarations and
> capabilities. Users are only required to learn about the new declaration or
> capability that was introduced. Their existing knowledge of the
> scope-bounded capability access control system is immediately applicable.
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#detailed-design>Detailed
> design
> <https://github.com/anandabits/swift-evolution/blob/scope-bounded-capabilities/proposals/NNNN-scope-bounded-capabilities.md#rules>
> Rules
> The rules which make up the *essential* complexity in Swift's access
> control system are:
>    - The default scope for all capabilites a declaration offers is
>    module-wide (or submodule-wide in the future).
>    - The scope of a capability may be modified by an explicit access
>    modifier.
>    - The scope of an *additional* capability is implicitly bounded by the
>    scope of the *basic* capability.
>    - The scope of an *additional* capability may not be explicitly
>    specified as greater than that of the *basic* capability.
>    - If no scope is explicitly provided for the *basic* capability and an
>    *additional* capability is specified to be available outside the
>    (sub)module the *basic* capability is also given the same availability.
>    - The scope of a declaration (including all capabilities) may be
>    bounded by the declaration of ancestor.
>    - The scope of a declaration may not be greater than the scope of the
>    capabilities necessary to use that declaration: if you can't see a
>    parameter type you can't call the function.
> Most of these rules already exist in Swift's access control system. There
> is one change and one addition:
>    - The first rule changes
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170303/40ecb8b0/attachment.html>

More information about the swift-evolution mailing list