[swift-evolution] [Pitch] consistent public access modifiers
david at alkaline-solutions.com
Wed Feb 15 14:47:27 CST 2017
Types have a ton of different implicit/explicit “API”, and access control modifiers often implicitly define that API:
- API for everyone to use when dealing with your concrete type (“public”)
- API for module code when you have related, coupled types that need a higher degree of knowledge (“internal”)
- API for within the implementation of the type itself (“fileprivate”/“private”)
- API to other code to interact with your type in a more general manner (protocol implementation)
- You also have two versions of each of these - instance and static/class-level properties/methods (including initializers)
- Each of these can also have stability aspects - which versions of a framework support the API, whether the API is deprecated or obsoleted, etc. (exposed partially today via “#available")
And classes add even more!:
- Whether subclassing is allowed (‘final’)
- API for subclasses to use for their implementation, but not meant for general usage (typically “protected")
- API which subclasses are allowed to override to implement new logic (“open")
- API which subclasses are forbidden to override because they define business logic used by coupled code ("final"/"closed")
- API which subclasses are required to override (typically “abstract" base classes - Swift and Objective C seem to prefer Delegates instead)
(I’m probably forgetting a few)
So access levels serve three main purposes:
1. to define these API so that a developer interacting with your type knows what is or is not (for instance) a subclass knows what it is or is not allowed to change, code using your types know what is or is not safe to call, etc.
2. to try to enforce these API to be used only by the intend audience
3. to prevent reliance on implementation details as a stable API
Obviously not all of these cases need compiler-enforcement of the API - nor could you have a simple enough system for general purpose consumption which attempted to do so. In the case language features do not document the stakeholders or behavior of the API, regular documentation and processes should attempt to do so.
This IMHO was the majority of the argument against SE-0025 - that if you are already in the same file, you must know the implementation details well enough to know what is or is not safe API. If developers were putting too much code within a single file was a case for a level above fileprivate, not below it. This why I personally pushed to defer until there was a submodule design.
The public/internal/private model is nice because it mirrors code locality, and thus is focused on enforcement of safety. If some other code depends on implementation details of my type that I don’t want to expose to the world, that code is going to be in the same module or even the same file. Hiding implementation details is enforcement for safety.
Classes obviously provide an explosion of complexity in defining behavior because of the additional relationship with sub- and super-classes. I generally push people away from designing their packages to rely on subclassing (instead preferring protocols and aggregation) because keeping this complexity straight and having a good design that reduces coupling is so difficult when dealing with subclassing.
More information about the swift-evolution