Access Control Modifiers Proposal Thoughts

The big thread these days on swift-evolution is regarding access control modifiers. Swift supports a fairly limited set today, namely:

  • public: visible outside of the module
  • internal: visible within the module
  • private: visible within the file

There is a proposal, SE-0025: Scoped Access Level, that wants to add another layer to the mix: lexical scoping (I’ll use local in the example for it).

struct Outer {
    local let scopeVisible: Int
    private let fileVisible: Int
    func f() {
        /* scopeVisible is accessible here */
        /* fileVisible is accessible here */

let o = Outer()
/* o.scopeVisible is not accessible here */
/* o.fileVisible is accessible here */

Today, in Swift, there is no way to provide this type of scoping mechanism within the same file.

My argument is that this proposal should be rejected, the Core Team thinks otherwise:

To summarize the place we’d like to end up:

  • “public” -> symbol visible outside the current module.
  • “internal” -> symbol visible within the current module.
  • unknown -> symbol visible within the current file.
  • “private” -> symbol visible within the current declaration (class, extension, etc).

The problem, as I see it, is that this is simply a one-off fix for one of the limitations for access modifiers today. There are other, arguably reasonable, asks for access control modifiers as well:

  • visibility to only extensions declared in the same file
  • visibility to only extensions
  • visibility to subclasses
  • visibility to specific functions or types (e.g. C++’s friend)

We could get even more fine grained as well:

  • visibility to only a specific set of modules
  • visibility within a specific submodule

I don’t think that these are all necessarily bad, in fact, some can be quite helpful. However, instead of just accepting this proposal and adding this specific change, I’d rather see the entire access modifier system to be revisited because this doesn’t really fix much, it just moves the problem.

The example used is this (where local means lexical scope):

class A {
   local var counter = 0

   // API that hides the internal state
   func incrementCount() { ++counter }

   // hidden API, not visible outside of this lexical scope
   local func advanceCount(dx: Int) { counter += dx }

   // incrementTwice() is not visible here

extension A {
   // counter is not visible here
   // advanceCount() is not visible here

   // may be useful only to implement some other methods of the extension
   // hidden from anywhere else, so incrementTwice() doesn’t show up in 
   // code completion outside of this extension
   local func incrementTwice() {

Ok, so this addresses the problem that counter is not meant to be visible outside of type A. Maybe it was unintentionally being leaked. However, if counter is required to be used within one of the extensions, say a reset() function, then counter needs to be promoted to the file-based one. However, by doing so, we are again leaking counter to be more visible than it is supposed to be. So what was really the point?

At the end, if the current access modifiers are not sufficient because they are “too leaky”, then this proposal doesn’t fix the root problem. If the root problem is really sufficient enough, then I would think all of the modifiers should be revisited to provide the fine-grained access control system that is really being asked for.

Of course, I’m also just fine with the three we have and not trying to add all of the complexity required.

If you have thoughts on it, be sure to contribute here:

Access Control Modifiers Proposal Thoughts