`extensionprivate` access control modifier

I am aware of the fact that there has been a lot of discussion related to this topic. However, such discussions always quickly change into debates related to things like "whether fileprivate is a good idea", "differences between lexical access level and type-based access level" or "whether it is easy to understand and learn". And it seems that it has been quite a long time without any new discussions related to this topic, I'd like to talk about this requirement and possible proposals again.

Motivation

Some developers (including me) like to group related things in different files, especially when we are implementing multiple protocol conformance for a type, to prevent a file from becoming too long. Actually even Apple themselves are doing this. Just check the source codes of swift-foundation, they have Decimal.swift and Decimal+Math.swift. Such implementation may require accessing properties or other methods in that type. Since we are in another file, those members have to be declared as internal. Of course using internal is fine, but it causes the code-completion to present some garbage that we may never directly access when we are using the type in other places. Some developers may design the name of such member to start with _ (again, swift-foundation is doing this, such as Decimal._length), but it still looks not elegant enough. (similar goal as partial class in C# ? )

Proposed Solution

Note that this solution is not really something new since many people has already proposed similar things (such as the typeprivate modifier introduced by @userFortyTwo). The main goal of this post is to further clarify the motivation and talk about some common misunderstandings, but still I will put the proposed solution here just to make the post looks complete.

Introduce a new modifier extensionprivate that allow the member to be accessible from extensions in the same module

// in file1.swift
struct SomeType {
    extensionprivate var someProperty: String
}

// in file2.swift
extension SomeType: CustomStringConvertible {
    var description: String { "SomeType(\(someProperty))" }    // this is ok
}

// in file3.swift
func example() {
    SomeType(someProperty: "").someProperty    // this is an error
}

This new access control modifier offers the same protection as internal:

  • can be accessed anywhere in the same declaration body
  • can be accessed anywhere in the same file
  • can be accessed in extensions in any files in the same module
  • CANNOT be access from another module

Some Common Misunderstanding

  1. This new modifier has nothing to do with private and fileprivate, in other word, it does NOT mean accessing private members. If you are sure that a member should never be accessed from the other file even in extensions, just keep using private and fileprivate. If you want the member to be accessible anywhere in the same module, just keep using internal. My point is that this new modifier does not break the behaviour of any existed modifiers, so even if you don't want to use it and prefer to write everything in the same file, it won't stop you from doing that.
  2. It is true that this modifier can be easily bypassed by defining an internal or even public function in an extension in the same module and expose it. However, as I have mentioned before, this modifier provide the same protection level as internal, so you are not really bypassing anything, this is just something expected. Besides, if some "bad guys" is able to write extensions for your type and access internal level members, they already have write access to your source codes, then they can simply write public functions in the same file of the type to expose all your private members. So such concern makes no sense to me. The aim of this modifier is to allow easily writing extensions in multiple files without having to declare members as internal and see "garbage" in code completion.
  3. Many people proposed introducing submodules to Swift. It might also be a good solution, but it requires additional configurations or definitions, and we have to write more import statement, which can be a little annoying.
  4. There are also some complains about the increased complexity, which make it harder to understand. Well, since extensionprivate has the same naming format as fileprivate and it actually has the same protection level as internal, it should not be too hard to understand, right?

Further Consideration

There is still one thing of this new modifier that needs further discussion: should a member declared as extensionprivate be directly accessible from its sub classes? Personally I will vote for yes since subclassing is also a form of extending a type?

3 Likes