If possible I would love to know the answer to my question that I asked in response to this statement. I made this a separate thread due to the understandable complaint made in the original thread about the question being quite off-topic.
I can’t swear I’m 100% on the precise definition of “lexical scope”, but if I’m understanding it correctly then wouldn’t that imply that putting
private
on the computed property itself instead of on the extension would cause the declaration to be private to that particular extension? (Which of course is not currently how it works)
It is how it works, with a subsequent minor modification: because that design would be of limited use, we've decided to take the union of all extensions in the same file to be one scope.
Wow. All these years I would've sworn that there is absolutely no difference between applying private
to the extension versus to each declaration individually, but lo and behold, it turns out I just never checked...
The following doesn't compile!
func checkIsEven (_ int: Int) -> Bool {
int.isEven // Error: 'isEven' is inaccessible due to 'private' protection level
}
extension Int {
private var isEven: Bool {
self % 2 == 0
}
}
Thank you for the insight
I know this is a well-worn topic, but I still often bump into the absence of a “typeprivate” scope. For example, we are encouraged to mark state or environment variables private
in SwiftUI (and indeed if we don’t then the view struct gets an inappropriate memberwise initializer), but doing so makes it impossible to place functions & computed properties that use those state variables into an extension to the view in a separate file.
It would be so useful to be able to mark a property as accessible only from within the same declaration or in-module extensions to that declaration.
Again, I’m aware this has been talked to near-death. But hope springs eternal.
Objections I’ve seen in the prior extensive threads (beyond bare statements that Swift’s visibility system uses lexical and not type-based scopes) include:
-
The current behavior of
private
is essential to be able to know that the implementation details of a type can’t be fiddled with from outside the file. Which is true but does not preclude the utility of a separatetypeprivate
visibility level. -
The arbitrariness of a file scope makes it a useful place to define access control boundary. Again, true but doesn’t address the problem that a single file can get bloated and it would be useful to allow specific members to be designated as accessible from extensions in other in-module files.
-
“[I]f the enclosing type doesn’t quite encompass the code that ought to have access to the declaration, you either need to move declarations into/out of the type to grant/restrict access, or you need to abandon
typeprivate
.” Okay—but it’s extremely common that the enclosing type does encompass the code that ought to have access to the declaration; we just want to split parts of a type’s definition into multiple files.
There are at least two reasonable counterarguments.
- If a file is getting too long to be manageable, it indicates that the type itself is too complex, and should be reconceived. This is likely true in many cases, but not others. For example, to break a SwiftUI subview out into its own
View
struct often requires a careful dance to pass down the relevant state variables, and it’s unfortunate to have to do that when the subview has complex logic but is semantically part of the main view and dependent on its state. - The difficulty in working with a long file comes from tooling problems and should be solved in that domain.
It’s too bad typeprivate
has been rejected and is unlikely to be revisited anytime soon.
typeprivate
is as needed as typed throws IMO, and the lack of it is one of the biggest oversights of the language.
Personally, I remain hopeful that we might someday get a form of submodules that amounts to “All files within this folder are treated as a single logical unit”. Then an access level like submoduleprivate
(hopefully with a nicer name) could subsume the role you are describing.
For example, by my count the standard library currently has 30 (yes, thirty!) .swift files whose names begin with “String” (and a further 6 more files whose names end in “String”). They could be organized into a String subfolder, and there are probably a fair number of things in those files that are currently internal
but would be better off as “only available to files in the String subfolder”.
I think this is a better solution than what you are asking for.
That would be great. I meant to say, but don’t think I really did, that I feel a lot more confident about the existence of the problem than about the details of a solution.
Why would you need a submoduleprivate
instead of just having the symbols be internal
to the submodule?
I intentionally picked an unambiguous but terrible spelling for illustrative purposes.
If you mean something like internal(String)
then sure, that could fall under “nicer name than submoduleprivate
”.
I’d want the bare internal
to retain its existing meaning though.
If we had submodules, then internal
at the top level of a submodule making a symbol only visible inside that submodule would be it's existing meaning.
I was using “submodule” to mean “a grouping smaller than a module”, and thus not itself a module.
In the String
example, internal
would continue to mean “visible throughout the standard library”, and some other keyword would mean “visible only to the String
grouping”.
If you feel strongly that a submodule must itself be a module, then pretend I used a different word instead of “submodule”.
folderprivate
would be an appropriate new access scope to achieve the same end.
Like fileprivate
, it has the advantage over private
of being context-insensitive and fundamentally intuitive. Even if you know nothing about access control at all, seeing fileprivate
on a declaration is still self-explanatory: "Well, I guess this is private to a file - which file? Well, presumably this file".
I feel strongly that we should not add any methods of grouping code that are not lexical scopes. So, I not only disagree with your phrasing, but with your idea itself.
I am not entirely clear on what you are objecting to. I am suggesting that we should allow folders (or submodules, or however we want to talk about them) to be treated as lexical scopes. The “folder” scope, or the “submodule” scope, or the “grouping of related files” scope.
One major benefit of what I am describing, compared to the original post of this thread, is that my idea uses a natural scope that exists between a file and a module, thereby maintaining Swift’s hierarchical system of access levels.
Are you suggesting that a module is somehow more of a lexical scope than a folder is?
over time i’ve come to the view that if you’re reaching for “submodules”, that’s a bright flashing indicator that your module has gotten too large and needs to be split into smaller modules.
while i too have been interested in submodules or some form of directoryprivate
in the past, i increasingly believe introducing such a thing would just encourage bad patterns.
I am stating outright that if private
, internal
, or public
don't effect symbol visibility the same way they do inside a module or a type, then it is not a lexical scope as currently exists in swift.
I have no idea what you are talking about.
All three of private
, internal
, and public
have some existing effect on symbol visibility, and I expect them to continue to have the same meaning in the future.
I am hopeful that at some point Swift will gain another level of visibility in between fileprivate
and internal
, for grouping closely-related components of a single module.
As an example, the standard library might want some way to group the 30+ files for String
so that their implementation details can be visible to each other but not the rest of the standard library.
I believe that the last thing we need is yet another access level. I'd much rather just have something like a C++ namespace
or a Rust mod
that keeps its internal symbols to itself, but lets you reexport its public symbols, with perhaps some extra features to allow you to replace an existing public symbol with a reexported one in exactly the way that C++s inline namespace
s don't manage to do.
In my opinion, the real reason for submodules/namespaces/what have you is to decouple linkage from namespacing. Library boundaries enforce a partial ordering, but it is quite likely that a vendor will want to use symbols in both directions across a submodule boundary.
To use a recent example, the standard library (module name Swift) will likely want to use Atomics, and the implementation of Atomics will likely rely on other features of the standard library. Yet the current model forces the Swift module to be built before Atomics, prohibiting such a cyclical dependency.
this doesn’t help Atomics
, but one pattern i have found helpful is to have a single-type module that just defines the uninhabited namespace enum, and then place an
exports.swift
@_exported import enum MyNamespace.MyNamespace
in the modules that use the namespace.
@_exported
has been given a terrible (and largely deserved) rap, but in my opinion this is one of the things it excels at.