[swift-evolution] A Comprehensive Rethink of Access Levels in Swift

David Waite david at alkaline-solutions.com
Sat Feb 25 00:12:33 CST 2017

> On Feb 23, 2017, at 2:56 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution at swift.org> wrote:

> The prevailing view from recent discussions is that there should be just one access level more fine-grained than ‘internal’, and it should be spelled ‘private’. Let us leave aside for the moment what its exact meaning should be, and consider the other end of the scale.

I think the submodule discussion now goes against this being a prevailing view - submodules are directly tied to be an access control level *above* private/fileprivate.

My expectation is that with submodules, “fileprivate” would go away.

> Moreover, object-oriented programming is just as much a first-class citizen in Swift as protocol-oriented programming is, so we should treat it as such. Classes are inherently inheritable: when one writes “class Foo {}”, then Foo has a default visibility of ‘internal’, and by default it can have subclasses. That is a straightforward model, and it is easy to work with.

There is nothing about OOP which requires subclass-based polymorphism. Rust and Go are two examples of modern languages that do not support subclassing at all.

In reality, there is less agreement on what “Object Oriented Programming” means to technologists than there is about the real meaning of “Moore’s Law"

> One of the reasons ‘public’ was previously chosen for closed classes is to provide a “soft default” for library authors, so they can prevent subclassing until they decide later whether to allow it in a future release. This is a misguided decision, as it prioritizes the convenience of library authors over the productivity of application developers. Library authors have a responsibility to decide what interfaces they present, and we should not encourage them to release libraries without making those decisions.

I think SE-0117 and the discussion behind it is pretty clear about why this is the default. It is not about convenience, but safety.
> Moreover, we need to trust client programmers to make reasonable choices. If a library mistakenly allows subclassing when it shouldn’t, all a client has to do to work with it correctly is *not make subclasses*. The library is still usable. Conversely, if a library mistakenly prohibits subclassing, then there are things a client *should* be able to do but cannot. The harm to the users of a library is greater in this last case, because the ability to use the library is compromised, and that diminishes their productivity.

If a client uses a library type which was never intended to be subclassed and subclasses it, the only options to take the library forward are to:
1. break the client
2. make that usage part of your supported API, and work around any issues that causes

I sympathize with app developers under deadlines. I wouldn’t mind Swift having an ‘escape hatch’ to let an app developer take responsibility and to then abuse a library’s access levels (if that were possible without restricting optimizations in the Swift compiler). But closed-by-default is safer, and lets poorly specified libraries evolve to be higher quality libraries without breaking existing usage that they never intended to support.

> We should not make “soft defaults” that tend to negatively impact the clients of a library for the dubious benefit of enabling its author to procrastinate on a basic design decision. If someone truly wants to publish a library with a closed class, then we should support that. But it should be an intentional decision, not a default.

If you were proposing the default for public classes be final rather than closed, I would be on-board with that line of thinking.

If you proposed that final was redundant and should go away, leaving just public and public open, I’d find that defensible (I think even with closed as a default, it is useful to indicate within am module that a class is subclassed or not) 

If you proposed that library authors should be forced into a decision, and should get a warning with a default of final until they label public classes as open or final or sealsed, I’d also be on-board with that option.

However, “open by default” is quite different than “final by default”. The default for structs and enums are final by default. Protocols are implementable, but they by definition should have defined semantics for doing so. Open-by-default subclasses have the potential to be modified in ways you were never expected, then handed back to you. Exposing a class the same way you would expose a struct, enum, or protocol and having it directly impact your resiliency because you forgot to also add “final” is just not as safe. I think the SE-0117 discussion covered this *very* well.

> Motivation – Rethinking ‘private’
> Now let us return to ‘private’, which as discussed earlier should be the only modifier that is tighter than ‘internal’. The purpose of ‘private’ is to enable encapsulation of related code, without revealing implementation details to the rest of the module. It should be compatible with using extensions to build up types, and it should not encourage overly-long files.

As said before, I think this is an incorrect conclusion. 

My personal thinking has been swayed over the last few months, and I’ve gone from vehemently opposing private to seeing merit in it. I believe my opposition was not against scoped private, but that it both kept fileprivate as a required access level and a redundant one, because it was not part of a larger change. 

Swift need a modifier between private and internal, and “fileprivate” is neither the feature we want, nor the spelling. Fileprivate should have been replaced with a more appropriate access level between private and internal, to represent ‘friend” relationships. I think we may be now looking at doing that, with submodules. 

My tact is that keywords should first and foremost document developer intent.

If I were to say which members of a type *should* be private, it would be the members that are not properly designed to uphold class invariance. Allowing access to write a negative integer to a property that should only ever be positive would be a good example. Calling a member without holding a lock or first dispatching onto the right GCD queue would be another.

In that context, a private which restricts even same-file, build-up extensions could be a positive language feature, because the extensions are being built using the safer interface. This is a trade-off, as some interfaces (NSCoding is a prime example) may require access to members otherwise made private.

> The natural definition, therefore, is that ‘private’ should mean “visible in a small group of files which belong together as a unit”. Of course Swift does not yet have submodules, and is not likely to gain them this year. However, if we say that each file is implicitly its own submodule unless otherwise specified, then the model works. In that view, ‘private’ will mean “visible in this submodule”, and for the time being that is synonymous with “visible in this file”.

I think internal is a more appropriate visibility within a submodule.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170224/6d123ca5/attachment.html>

More information about the swift-evolution mailing list