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.
-
Apple Developer Documentation — Xcode Build Settings Reference (module and target configuration referenced by access control).
-
Swift.org — Swift Package Manager (packages and targets that
packageaccess is scoped to).