Skip to content

Instantly share code, notes, and snippets.

@NSExceptional
Last active December 6, 2018 12:23
Show Gist options
  • Save NSExceptional/6042297e5ad2b0a935a8d9123c19c820 to your computer and use it in GitHub Desktop.
Save NSExceptional/6042297e5ad2b0a935a8d9123c19c820 to your computer and use it in GitHub Desktop.
My proposal for better access control in Swift.

Problems with Swift's access control

  • Classes being "closed" by default is unusual in most OOP languages and allows for a careless developer to unknowingly write a library that restricts how it can be used by not making classes open as needed.
  • Swift's distinction between file-private and declaration-private scopes is confusing (fileprivate and private as of Swift 3) and completely unnecessary most of the time, since when one includes two top-level declarations in the same file, it's usually because the declarations are supposed to be visible to each other.

Proposed solution

  • open: all declarations are implicitly open; no open keyword.

  • final: inherits implicit visibility when none is specified.

    • Final classes cannot be subclassed (structs, enums, global funcs, etc are implicitly final).
    • Final properties and methods cannot be overridden by subclasses.
  • public: visible to every file in all modules.

  • internal: default visibility for top-level decls when none is specified.

    • Internal top-level declarations visible to same module only.
    • Internal nested decls (properties, methods, inner types) visible to same file and subclasses ONLY (not applicable to structs), behaves like Java's protected keyword.
  • private: visible to same file only.

    • Private type members are implicitly final.

Nested decls inherit parent decl visibility, except when the parent decl is public. All public members and types must be declared public explicitly. Members of an internal type are visible wherever the parent decl is visible.

// Implicitly internal and open.
class Foo {
    // Visible where Foo is visible
    var name = ""
    
    // Visible ONLY to subclasses and same file
    internal var id = 5
    
    // Visible to same file only, implicitly open
    private func bar() {}
    
    // Error: conflicting visbility with parent decl
    public func bar(_ x: Int) {}
    
    // Visible to subclasses and same file, cannot be overridden.
    internal final func cannotOverride() {}
}

// Implicitly open
internal class SameVisibilityAsFoo {
    
}

// Visible everywhere, not subclassable
public final class Bar {
    // Implicitly internal and final
    var name = ""
    
    // Error: parent decl is final, use private instead.
    internal var color = "red"
    
    // Warning: `final` specifier is redundant
    // when applied to member of a `final` type
    private final var id = 5
}

// Visible to same file, implicitly open
private class Baz {

}

// Visible to all modules
public func abc() {}
// Implicitly internal, visible to whole module only
func def() {}
// Same as above
internal func ghi() {}
// Visible to same file only
private func jkl() {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment