Access Control

This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Access control restricts which parts of a program can see a given declaration from other source files and modules, letting a type or module hide its implementation details behind a deliberate public interface.

Modules, Source Files, and Packages

A module is a single unit of code distribution — an app, a framework, or a library built and shipped as one unit, imported elsewhere with import. A source file is a single .swift file inside a module; several access levels below are scoped to one or the other. A package, introduced alongside the package access level, is a looser grouping of one or more targets/modules that are developed together (e.g. a Swift package’s several library targets) but distributed as more than one module — access control’s package level exists specifically to share code across that boundary without exposing it as public to consumers of the package as a whole.

The Six Access Levels

open class Vehicle {}                 // open: subclassable/overridable outside the defining module too
public class Bicycle {}               // public: usable outside the module, but not subclassable/overridable there
package struct InternalMetrics {}      // package: visible anywhere in the same package, not outside it
internal struct Config {}              // internal: visible anywhere in the defining module (the default)
fileprivate struct Cache {}            // fileprivate: visible only within this source file
private struct Secret {}               // private: visible only within the enclosing declaration (and its extensions in this file)

From least to most restrictive: open and public both let a declaration be used from any module that imports the defining one; open additionally allows it to be subclassed or overridden from outside the defining module, while public only allows that within the defining module itself — open exists as a distinct level specifically to make "designed for external subclassing" an explicit, opt-in choice rather than an accident of also being usable. package allows access from any file within the same package, regardless of module, but not from outside the package — useful for sharing code across a package’s own multiple targets without making it part of that package’s public API. internal (the default when no modifier is written) allows access anywhere within the defining module, but not outside it. fileprivate restricts access to the enclosing source file, regardless of which type or extension within that file. private is the most restrictive: access only within the enclosing declaration, and (as a specific carve-out) its extensions when those extensions live in the same file.

The Guiding Principle and Defaults

The guiding principle behind every level: no entity can be defined in terms of another entity with a lower (more restrictive) access level — a public function cannot have an internal-only parameter type, and a internal variable cannot be of a private type, since either would let external code observe or accept a value of a type it cannot itself name. internal is the default access level for almost every declaration with no explicit modifier, chosen because most types in an app or framework are meant to be used throughout that module without extra boilerplate, while still not leaking out to other modules by accident.

Access Levels for Apps, Frameworks, and Test Targets

An app’s executable target typically needs nothing above internal — nothing outside the app ever imports it, so public/open add nothing. A framework (or library) meant for external consumption instead needs its genuine API surface marked public (or open where subclassing/overriding is deliberately supported) — anything left internal remains an implementation detail invisible to the framework’s own consumers, which is the whole point of drawing that line explicitly rather than exposing everything.

@testable import MyFramework    // lets this test target see MyFramework's `internal` declarations too

A test target is the one situation where access control is deliberately relaxed from outside a module: @testable import grants a test target visibility into the imported module’s internal (but not private/ fileprivate) declarations, so tests can exercise implementation details a framework’s real external consumers never see — available only for modules compiled with testing enabled (the default for a package’s own test targets and for Debug builds), never for a release build meant for distribution.

Access Levels for Custom Types

A type’s own access level sets a ceiling for its members: a fileprivate struct’s members can be no more accessible than `fileprivate, regardless of what modifier they carry individually, because nothing outside the file could ever construct or reference the type in the first place to reach a more visible member. Tuple types have no explicit access level of their own — a tuple’s access level is inferred as the most restrictive access level of the types composing it, so a tuple containing one private type is itself unusable anywhere the private type couldn’t already be named.

Functions

private func minMax(_ array: [Int]) -> (min: Int, max: Int) {   // return type's access must be >= the function's
    (array.min()!, array.max()!)
}

A function’s access level is computed the same way as a tuple’s members: the most restrictive access level among its parameter types and its return type sets a floor the function itself cannot go below without also restricting those types — a function cannot be more visible than a type it accepts or returns, since callers who could see the function but not the type would have no way to actually use it.

Enums, Nested Types, and Subclassing

public enum CompassPoint {          // a case's access level is always exactly its enum's -- cases can't be tagged individually
    case north, south, east, west
}

public class SomeClass {
    fileprivate struct SomeStruct {}      // a nested type's default access is the same as its enclosing type's
}

public class SomeSubclass: SomeClass {}    // subclassing across modules requires the superclass to be `open`

An enum case’s access level is always identical to its enclosing enum’s — individual cases cannot be given a different access modifier, since a case is not useful at all without visibility into the enum that defines it. A nested type's default access level matches its enclosing type’s (unless the enclosing type is public, in which case a nested type defaults to internal rather than inheriting public automatically) — explicit about which nested types are meant to be part of the public surface. Subclassing a class from a different module requires that class to be open, not merely public; within the same module, public (or any less restrictive level) is sufficient, since the whole distinction open draws is specifically about visibility to code outside the defining module.

Properties, Getters/Setters, and private(set)

public struct TrackedString {
    public private(set) var numberOfEdits = 0    // readable anywhere TrackedString is visible, settable only from within
    public var value: String = "" {
        didSet { numberOfEdits += 1 }
    }
}

A stored property’s getter and setter share its declared access level by default, but a setter can be given a more restrictive level than its getter (never the reverse) with private(set)/fileprivate(set)/ internal(set)/package(set) written before var — public private(set) var numberOfEdits above lets any code that can see TrackedString read numberOfEdits, while only code inside `TrackedString’s own file can assign to it, which is exactly the shape a read-only-from-outside counter needs without resorting to a separate computed property and a private backing store.

Initializers, Protocols and Conformance, Extensions, Generics, and Type Aliases

public struct Point {
    public var x = 0, y = 0
    public init() {}                          // a public type needs an explicit public init to be constructible externally
}

public protocol Loggable {                     // a protocol's requirements always match the protocol's own access level
    func log()
}

extension Point: Loggable {                    // conformance access = min(the type's access, the protocol's access)
    public func log() { print("(\(x), \(y))") }
}

private extension Point {                       // a private extension restricts everything it adds to this file
    func distanceFromOrigin() -> Double { (Double(x * x + y * y)).squareRoot() }
}

public func firstElement<T>(of array: [T]) -> T? { array.first }    // generic access follows the same floor rule as parameters

public typealias Coordinate = Point              // a type alias's access can be no more permissive than what it aliases

A structure’s automatically synthesized memberwise initializer is only as accessible as the type itself needs to construct it externally — a public struct still requires an explicitly written public init() (or memberwise initializer marked public) to be constructible from outside its module, since the compiler’s default synthesized initializer is internal regardless of the type’s own access level. A protocol’s requirements always share the protocol’s own access level exactly — a requirement cannot be individually more or less restrictive, since every conforming type must be able to see and implement every requirement. Conformance itself (a type conforming to a protocol) can be scoped to be less visible than either the type or the protocol alone (e.g. extension Point: Loggable written in one file with public members, or restricted further with a private extension), which effectively controls where the conformance itself is visible independently of the type and protocol’s own access levels. Generic types and functions follow the same "no more permissive than its least accessible constituent part" floor as ordinary functions, applied to their type parameters' constraints too. A type alias obeys the ceiling rule as well: it can be declared with any access level up to (but not exceeding) the access level of the type it aliases, letting an alias re-expose a type under a more restrictive name for use only within a smaller scope than the original type’s own.

Best Practices

Default to the most restrictive access level that still lets the code compile and be used the way it needs to be — start new declarations private or fileprivate and widen only when an outside caller genuinely needs access, rather than starting everything public and hoping nothing leaks. Reserve open specifically for base classes and methods a framework intends external code to subclass or override — widening to open later is a source-compatible change, whereas narrowing from open to public is not, so it costs nothing to start narrower. Treat package as the boundary for a multi-target Swift package’s own internal sharing, not as a looser synonym for public — code meant for a package’s external consumers still belongs at public/open.

See Also

  • Structures and Classes — the synthesized memberwise initializer whose own access level this page qualifies.

  • Protocols — protocol requirements and conformance in full, independent of the access-control rules covering them here.

  • Extensions and Nested Types — extension syntax and nested-type scoping this page’s access rules apply to.

  • Swift Package Manager — packages, targets, and modules as Swift Package Manager actually builds them.

References

TSPL: Access Control.