Question about the vision for `private`

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.

2 Likes

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.

2 Likes

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 :pray:

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:

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.

2 Likes

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.

11 Likes

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.

1 Like

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”.

1 Like

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".

1 Like

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.

2 Likes

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 namespaces don't manage to do.

2 Likes

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.

3 Likes

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.