[swift-evolution] SE-0025: Scoped Access Level, next steps

Brent Royal-Gordon brent at architechies.com
Wed Mar 30 18:30:03 CDT 2016

>> Actually, I can think of one very interesting boundary that roughly parallels the SPI/API boundary: the boundary between resilience domains. In other words, "public within resilience domain" may be a useful access level.
> Hm. That may indeed be useful because it allows the compiler to omit resilient information about the declaration. I'm worried that's just adding more knobs that won't really be a benefit in practice, though.

I think it would be a benefit because it could be used to model the distinction between SPI and API and to omit SPIs entirely from the released swiftmodule, as you suggested Apple (and perhaps others) would need. It'd be nice to have that as a formal part of the language, natively supported by all the tools.

>> Mostly, it just bothers me that we have these two middle access levels (`moduleprivate` and `fileprivate`) which seem closely related compared to new-`private`, yet are both completely ad-hoc and elude decent naming. Calling them, say, `internal` and having `internal(#file)` be a special case of `internal(file names here)` seems like a nice way to rationalize them.
> Can you elaborate on why "moduleprivate" and "fileprivate" are closely related for you? To me all three of the proposed non-public levels are "concentric circles"; one of them is bounded by braces, one by the filesystem, and one by the files in your target (as described by Xcode or the package manager or an ad hoc build).

I guess the way I see it is this:

Consider the inside of a function body. The things in it are lexically scoped: they are visible within the scope, including in nested scopes, but not outside it. Inner blocks can access the variables of outer blocks, but not vice versa. Because a function body is built from anonymous stack frames, it cannot really be made more globally visible, so we don't need to explicitly ask for this behavior.

Named module-wide entities, on the other hand, *are* nailed down to a stable location, so they *can* be made more globally visible. Thus, we do need access control keywords.

In my view, `private` is basically asking for the same behavior seen elsewhere in the language: lexically scoped access. The others—`fileprivate`, `moduleprivate`, and `public`—are asking for that behavior to be varied by exposing the symbol they're decorating more widely than normal.

Now, there is a very important distinction between `public` and the others. A public symbol is exposed to the world; at compile time, it is impossible to know what will use it. `public` represents a veil of ignorance for the optimizer.

But between these two natural boundaries—the complete visibility and ignorance of `public` and the strict, natural lexical scope of `private`—any access levels we introduce are artificial. Especially now that we have WMO, `fileprivate` is just `moduleprivate` with an artificial visibility limit. That limit could just as easily be to an enumerated list of files, to direct ancestor scopes (and their non-type child scopes), to extensions, to subclasses, to particular symbols, or to odd-numbered lines. We choose "the current file" because that happens to frequently be useful, but there's nothing magical about the current file that makes it especially appropriate.

I guess that's why I keep wanting to unify `fileprivate` and `moduleprivate` somehow: because I see `fileprivate` as an artificial limitation of `moduleprivate`, and it seems to me that other such limitations would be equally valid.

* * *

While working that out, I also came up with a new suggestion for keywording if we keep just these four levels and don't generalize `fileprivate` into an "internal, with limitations" mechanism:

* public
* moduleinternal
* internal
* private

This version saddles only the module access level, which rarely needs to be typed explicitly, with an awkward compound keyword. The file access level (which I suspect is more often necessary than many in this thread believe) gets a single, simple keyword. Additionally, by having only one gobbledygook access level, when you *do* see the gobbledygook keyword you don't have to try to parse it.

This does have the disadvantage of not matching C#'s meaning of `internal`; we could do something else, like `shared`/`moduleshared`, instead. But I'm not sure how valuable that correspondence is.

Brent Royal-Gordon

More information about the swift-evolution mailing list