Swift (programming language)

Reference entry, current to Swift 6.3.3 (30 June 2026) with the 6.4 beta toolchain noted where behaviour differs. This article follows the standard entry structure described on the Overview page. Toolchain, package-manager, attribute, operator, and API-naming tables are on the companion Swift reference page.

Swift is a high-level general-purpose, multi-paradigm, compiled programming language created by Chris Lattner in 2010 for Apple Inc. and maintained by the open-source community. Swift compiles to machine code through an LLVM-based compiler and its own intermediate representation, SIL. It was first released in June 2014, and the Swift toolchain has shipped in Xcode since Xcode 6, released in September 2014.

Apple intended Swift to support many core concepts associated with Objective-C — notably dynamic dispatch, widespread late binding, and extensible programming — but in a safer way that makes software bugs easier to catch. Swift has features addressing common programming errors such as null pointer dereferencing, and provides syntactic sugar that helps avoid the pyramid of doom. Swift supports protocol extensibility, an extensibility system that can be applied to types, structs, and classes, which Apple promotes as a change in programming paradigms it terms protocol-oriented programming, similar to traits and type classes.

Three properties distinguish Swift from the other languages in this atlas. First, value semantics are the default: struct, enum, and the standard collections are copied rather than shared, with copy-on-write making the copy lazy. Second, memory is managed by Automatic Reference Counting rather than a tracing collector, giving deterministic destruction and a small, predictable runtime — the property that makes the language viable for both embedded targets and server workloads. Third, since the Swift 6 language mode released in September 2024, data-race freedom is checked by the compiler rather than left to convention, which makes Swift one of only two mainstream languages — with Rust — to enforce it statically.

History

Development of Swift started in July 2010 by Chris Lattner, with the eventual collaboration of many other programmers at Apple. Swift was motivated by the need for a replacement for Objective-C, which had been largely unchanged since the early 1980s and lacked modern language features. Swift took language ideas “from Objective-C, Rust, Haskell, Ruby, Python, C#, CLU, and far too many others to list”. On June 2, 2014, the Apple Worldwide Developers Conference application became the first publicly released app written with Swift, and The Swift Programming Language — a free 500-page manual that remains the normative language guide — was published at the same conference.

Swift reached 1.0 on September 9, 2014, with the Gold Master of Xcode 6.0. Through version 3.0 the syntax evolved substantially and source compatibility was explicitly not promised; after 3.0 the core team made source stability a priority, and every release since has shipped with a migrator for the changes it does make. In the first quarter of 2018, Swift surpassed Objective-C in measured popularity.

Swift 5 (March 2019) delivered ABI stability on Apple platforms, allowing the Swift runtime and standard library to ship inside the operating system rather than inside every app bundle. Swift 5.1 (September 2019) added module stability, making it possible to distribute a binary framework that keeps working with future compiler releases. Together these are the two properties that made Swift viable for shipping closed-source libraries.

Swift 5.5 (September 2021) introduced structured concurrency: async/await, tasks and task groups, actors, and the Sendable protocol. Swift 5.9 (September 2023) added the macro system, generic parameter packs, if and switch expressions, and the first ownership modifiers borrowing and consuming. Swift 5.10 (March 2024) completed full data isolation checking under the strict-concurrency flag.

Swift 6, released September 2024, introduced the Swift 6 language mode, in which data-race safety is enforced as an error rather than a warning; it also added typed throws and package-level access control. Swift 6.1 (April 2025) added some as lightweight generic syntax and the noasync availability argument. Swift 6.2 (September 2025) reworked the default isolation story around the main actor and added if case shorthand and implicit conformance suppression. Swift 6.3 (March 2026) is the current stable series, with 6.3.3 released on 30 June 2026; the 6.4 beta is the edition documented by the current book and shipped with Xcode 26.4.

Swift won first place for Most Loved Programming Language in the Stack Overflow Developer Survey 2015 and second place in 2016. On December 3, 2015, the language, supporting libraries, debugger, and package manager were open-sourced under Apache 2.0 with a Runtime Library Exception, and Swift.org was created to host the project. In January 2017, Chris Lattner left Apple and Ted Kremenek became project lead. At WWDC 2019 Apple announced SwiftUI, a declarative UI framework built on result builders and property wrappers. In October 2025 the Swift Android workgroup announced a preview release of the official Swift SDK for Android.

2010Work begins20141.0 at WWDC2015Open sourced2017Source stability2019ABI stability2021async / await2024Swift 6 mode
Figure 1. Swift's milestones, each chosen because it changed what the next release could assume. Source compatibility arrived before binary compatibility, and both arrived before the concurrency model that depends on them.

Back to top

Release model and language modes

Swift separates three things that other languages often conflate: the compiler version, the language mode a module is compiled in, and the upcoming feature flags a module opts into. A single compiler build accepts several language modes simultaneously, and a target written in one mode can depend on a target written in another. This is what allows a large project to migrate one framework at a time.

Set per module with -swift-version, or in a package manifest with swiftLanguageMode(.v5) on a target.

Language modeIntroducedWhat it changesData-race checking
Swift 42017Original migration target from Swift 3Still accepted by the 6.x compiler
Swift 4.22018Adds synthesized conformances and CaseIterableStill accepted
Swift 52019Default mode for existing code; concurrency available, checking is advisoryWarnings only for data-race issues
Swift 62024Full data-race safety enforced by the compilerErrors for data-race issues; opt in per module

Source: The Swift Programming Language, Version Compatibility. The Swift 6.4 compiler builds code written in Swift 6.4, 5, 4.2, or 4.

Within a language mode, individual behaviours from a future mode can be enabled ahead of time with upcoming feature flags, so that a module can be moved toward Swift 6 one diagnostic at a time rather than all at once. A package manifest expresses both:

// swift-tools-version: 6.0
import PackageDescription

let package = Package(
    name: "MyPackage",
    platforms: [.macOS(.v14), .iOS(.v17)],
    products: [
        .library(name: "Core", targets: ["Core"]),
    ],
    targets: [
        // Uses the default tools language mode (6): full data-race checking.
        .target(name: "Core"),

        // Not migrated yet: still builds in the Swift 5 mode, with warnings.
        .target(
            name: "LegacyImport",
            swiftSettings: [
                .swiftLanguageMode(.v5),
                .enableUpcomingFeature("InferSendableFromCaptures"),
            ]
        ),
        .testTarget(name: "CoreTests", dependencies: ["Core"]),
    ],
    swiftLanguageModes: [.v6]
)

Four distinct stability guarantees, frequently confused with one another.

PropertySinceWhat it guaranteesConditions
Source stabilitySwift 5 (2019)Code that compiles today keeps compiling with later compilers in the same language modeGuaranteed within a language mode; mode changes are opt-in
Module stabilitySwift 5.1 (2019)A .swiftinterface file lets a binary framework be imported by future compilersRequires -enable-library-evolution
ABI stabilitySwift 5 (2019)Binaries built with different compiler versions interoperate at runtimeApple platforms only; the runtime ships in the OS
Library evolutionSwift 5.1 (2019)A library can add members and reorder stored properties without breaking clientsCosts resilient (indirect) access unless @frozen

Sources: Swift.org release notes and the Library Evolution chapter of the Swift 6 migration guide. On Linux and Windows the runtime is distributed with the application, so ABI stability is not relied on.

Practical rule. @frozen and @inlinable are permanent promises to your clients: the former fixes a type's stored layout, the latter copies the function body into client binaries so that changing it does not change already-compiled callers. Neither can be withdrawn without a source- and binary-breaking change.

Back to top

Platforms and support tiers

A key aspect of Swift's design is its ability to interoperate with the body of existing Objective-C code developed for Apple products over previous decades, such as the Cocoa and Cocoa Touch frameworks. On Apple platforms it links with the Objective-C runtime library, which allows C, Objective-C, C++, and Swift code to run within one program. On every other platform Swift does not depend on an Objective-C runtime; a set of “corelibs” implementations — swift-corelibs-foundation, swift-corelibs-libdispatch, and swift-corelibs-xctest — stand in for the Apple frameworks.

Where the compiler runs, what it can target, and the oldest OS release a built binary supports. All rows use the same compiler front end and standard library source.

PlatformArchitecturesSupport levelMinimum deployment versionNotes
macOSarm64, x86_64Development and deployment13.0Full Darwin runtime, Objective-C interop; toolchain in Xcode and standalone
iOS / iPadOSarm64Deployment; cross-compiled from macOS16.0Swift runtime shipped in the OS since iOS 12.2
tvOSarm64Deployment; cross-compiled from macOS16.0Same runtime model as iOS
watchOSarm64Deployment; cross-compiled from macOS9.0Same runtime model as iOS
visionOSarm64Deployment; cross-compiled from macOS1.0Same runtime model as iOS
Ubuntuarm64, x86_64Development and deployment22.04Corelibs replace Foundation, Dispatch, XCTest; Static Linux SDK available
Debianarm64, x86_64Development and deployment12As Ubuntu
Fedoraarm64, x86_64Development and deployment41As Ubuntu
Amazon Linuxarm64, x86_64Development and deployment2023As Ubuntu
Red Hat UBIarm64, x86_64Development and deployment9As Ubuntu
Windowsarm64, x86_64Development and deployment10MSVC-compatible; no Objective-C runtime; official installer since 5.3
Androidarm64, armv7, x86_64Deployment; SDK from the Android workgroup9 (API 28)Official SDK preview since October 2025
WebAssemblywasm32Deployment; SwiftWasm SDKCommunity-maintained; WASI target, no threads by default
FreeBSDx86_64Deployment; community portCommunity-maintained; tracks the Linux corelibs
Linux on IBM Zs390xDeployment; community portCommunity-maintained; server workloads
Embedded (bare metal)ARM Cortex-M, RISC-VCompilation mode, not a platform portNo reflection, no existentials with unbounded layout, no runtime metadata

Sources: Swift.org “Platform Support” and the Swift SDK announcements, current to 6.3.3. Swift.org distinguishes platforms that run the development tools from those that can only be deployed to; rows below the third rule are community-maintained and are not listed on the official support page. Swift Package Manager and SourceKit-LSP are available on every development-and-deployment platform listed.

Static linking deserves separate mention: the Static Linux SDK produces a fully static binary against Musl with no shared-library dependency, which is the usual choice for container images and serverless deployment. Embedded Swift is not a separate language but a compilation mode that removes the features requiring runtime metadata — reflection, existentials with unbounded layout, and dynamic casting — to fit microcontroller-class targets.

20142017202020232026Mode 4 — legacy, still acceptedMode 4legacy, still acceptedMode 4.2 — legacy, still acceptedMode 4.2legacy, still acceptedMode 5 — default before 6Mode 5default before 6Mode 6 — opt in per moduleMode 6opt in per moduleSource stability — from 3.2 / 4.0Source stabilityfrom 3.2 / 4.0ABI stability (Apple) — from 5.0ABI stability (Apple)from 5.0Library evolution — opt in, resilient modulesLibrary evolutionopt in, resilient modules
Figure 2. Language mode lifetimes. Modes are cumulative, not successive: every mode the compiler has ever accepted is still selectable, per module, in the current release. The four stability guarantees below the rule are what make that possible, and they begin at different releases.

Back to top

Version history

Platform availability by release. Heavier rules separate major versions; the version column stays fixed while the table scrolls horizontally.

Swift version Release date macOS Linux Windows
1.0September 9, 2014YesNoNo
1.1October 22, 2014YesNoNo
1.2April 8, 2015YesNoNo
2.0September 21, 2015YesNoNo
2.1October 20, 2015YesNoNo
2.2March 21, 2016YesYesNo
2.2.1May 3, 2016YesYesNo
3.0September 13, 2016YesYesNo
3.0.1October 28, 2016YesYesNo
3.0.2December 13, 2016YesYesNo
3.1March 27, 2017YesYesNo
3.1.1April 21, 2017YesYesNo
4.0September 19, 2017YesYesNo
4.0.2November 1, 2017YesYesNo
4.0.3December 5, 2017YesYesNo
4.1March 29, 2018YesYesNo
4.1.1May 4, 2018NoYesNo
4.1.2May 31, 2018YesYesNo
4.1.3July 27, 2018NoYesNo
4.2September 17, 2018YesYesNo
4.2.1October 30, 2018YesYesNo
4.2.2February 4, 2019NoYesNo
4.2.3February 28, 2019NoYesNo
4.2.4March 29, 2019NoYesNo
5.0March 25, 2019YesYesNo
5.0.1April 18, 2019YesYesNo
5.0.2July 15, 2019NoYesNo
5.0.3August 30, 2019NoYesNo
5.1September 10, 2019YesYesNo
5.1.1October 11, 2019NoYesNo
5.1.2November 7, 2019YesYesNo
5.1.3December 13, 2019YesYesNo
5.1.4January 31, 2020NoYesNo
5.1.5March 9, 2020NoYesNo
5.2March 24, 2020YesYesNo
5.2.1March 30, 2020NoYesNo
5.2.2April 15, 2020YesYesNo
5.2.3April 29, 2020NoYesNo
5.2.4May 20, 2020YesYesNo
5.2.5August 5, 2020NoYesNo
5.3September 16, 2020YesYesYes
5.3.1November 13, 2020YesYesYes
5.3.2December 15, 2020YesYesYes
5.3.3January 25, 2021NoYesYes
5.4April 26, 2021YesYesYes
5.4.1May 25, 2021NoYesYes
5.4.2June 28, 2021YesYesYes
5.4.3September 9, 2021NoYesYes
5.5September 20, 2021YesYesYes
5.5.1October 27, 2021YesYesYes
5.5.2December 14, 2021YesYesYes
5.5.3February 9, 2022NoYesYes
5.6March 14, 2022YesYesYes
5.6.1April 9, 2022NoYesYes
5.6.2June 15, 2022NoYesYes
5.6.3September 2, 2022NoYesYes
5.7September 12, 2022YesYesYes
5.7.1November 1, 2022YesYesYes
5.8March 30, 2023YesYesYes
5.8.1June 1, 2023YesYesYes
5.9September 18, 2023YesYesYes
5.9.1October 19, 2023YesYesYes
5.9.2December 11, 2023YesYesYes
5.10March 5, 2024YesYesYes
5.10.1June 5, 2024YesYesYes
6.0September 17, 2024YesYesYes
6.0.1September 27, 2024YesYesYes
6.0.2October 28, 2024YesYesYes
6.0.3December 13, 2024YesYesYes
6.1April 1, 2025YesYesYes
6.1.1May 24, 2025YesYesYes
6.1.2May 28, 2025YesYesYes
6.1.3September 8, 2025YesYesYes
6.2September 17, 2025YesYesYes
6.2.1November 4, 2025YesYesYes
6.2.2December 9, 2025YesYesYes
6.2.3December 12, 2025YesYesYes
6.2.4February 27, 2026YesYesYes
6.3March 27, 2026YesYesYes
6.3.1April 17, 2026YesYesYes
6.3.2June 4, 2026YesYesYes
6.3.3June 30, 2026YesYesYes

82 releases, 1.0 (September 2014) to 6.3.3 (June 2026). Sources: Swift.org release announcements and the swiftlang/swift release tags; where an announcement and its tag differ by a day, the tag date is used from 6.0 onward. Windows support begins at 5.3; Linux support begins at 2.2.

The same history read as a feature timeline rather than a release list.

VersionReleasedPrincipal language additions
5.1September 2019Opaque return types (some), property wrappers, module stability
5.4April 2021Result builders, multiple variadic parameters
5.5September 2021async/await, structured concurrency, actors, Sendable
5.6March 2022Existential any spelling introduced
5.7September 2022Primary associated types, if let shorthand, regex literals, distributed actors
5.9September 2023Macros, parameter packs, if/switch expressions, borrowing/consuming
5.10March 2024Full data isolation under strict concurrency checking
6.0September 2024Swift 6 language mode, typed throws, package access level, noncopyable generics
6.1April 2025some as lightweight generic syntax, noasync availability
6.2September 2025Main-actor default isolation, value generics, if case shorthand, implicit conformance suppression
6.3March 2026Current stable series
6.4July 2026 (beta)Current book edition and the default in Xcode 26.4; corrections rather than new language surface

Source: Document Revision History of The Swift Programming Language. Only additions that changed the language surface are listed; each release also carried diagnostics, tooling, and performance work.

0368112014201620182020202220242026Releases · 2014: 22Releases · : 33Releases · 2016: 55Releases · : 55Releases · 2018: 66Releases · : 1111Releases · 2020: 1111Releases · : 88Releases · 2022: 77Releases · : 55Releases · 2024: 66Releases · : 88Releases · 2026: 55Releasesreleases
Figure 3. Releases per calendar year, counting every tagged release including patches. The 2019 and 2020 peaks are the ABI-stability and platform-expansion years; the recent floor of five to eight reflects a settled two-major-releases-a-year cadence with patch releases between them. Source: swiftlang/swift release tags.

Back to top

Language features

Swift is a general-purpose language that employs modern programming-language theory concepts and strives to present a simple yet powerful syntax. It was designed to be safe and friendly to new programmers without sacrificing speed: memory is managed automatically, variables are always initialized before use, array accesses are bounds-checked, and integer operations trap on overflow unless the wrapping operator is used explicitly. Protocols define interfaces that types may adopt, extensions add functionality to existing types, optionals make absence explicit in the type system, and actors isolate shared mutable state.

Safety by default

Definite initialization, bounds checking, trapping arithmetic, and exclusive access to memory are on unless explicitly opted out of with an Unsafe-prefixed API.

Value semantics

struct, enum, and the standard collections are value types; copies are lazy through copy-on-write.

Protocol orientation

Protocols with extensions, associated types, and generics replace multiple inheritance and most class hierarchies.

Compile-time concurrency

Isolation, Sendable, and actors are checked by the compiler in the Swift 6 language mode, not enforced by convention.

Basic syntax

Swift's syntax is similar to C-style languages. Code in main.swift begins executing in the global scope; elsewhere, the @main attribute marks the type that contains the program's entry point. Statements do not need semicolons except to separate multiple statements on one line. Constants are declared with let and variables with var; values must be initialised before they are read, and the type is inferred from the initial value when one is present.

let highScoreThreshold = 1000   // Constant, type Int inferred from the literal.
var currentScore = 980          // Variable, type Int.
currentScore = 1200

let playerMessage: String       // Declared now, assigned exactly once below.
if currentScore > highScoreThreshold {
    playerMessage = "You are a top player!"
} else {
    playerMessage = "Better luck next time."
}

print(playerMessage)            // Prints "You are a top player!"

Control flow uses if/else, guard, and switch, together with while, repeat-while, and for-in loops. Since Swift 5.9, if and switch are also expressions, which removes most uses of the ternary operator and of mutable temporaries:

// if and switch as expressions (Swift 5.9+)
let band = switch currentScore {
case ..<500:      "bronze"
case 500..<1000:  "silver"
default:          "gold"
}

// switch is exhaustive: the compiler rejects a missing case over a finite type.
enum Direction { case north, south, east, west }

func turn(_ d: Direction) -> Direction {
    switch d {
    case .north: .east
    case .east:  .south
    case .south: .west
    case .west:  .north
    }
}

guard requires a condition to hold before execution continues; its else branch must leave the enclosing scope. It is the idiomatic way to state a precondition and to produce an unwrapped optional that stays in scope afterwards — the inversion of the arrow-shaped nesting that if let would otherwise produce.

func divide(numerator: Int?, byDenominator denominator: Int) throws -> Int {
    guard denominator != 0 else { throw MathError.divisionByZero }
    guard let numerator else { throw MathError.missingOperand }
    return numerator / denominator   // numerator is a non-optional Int here.
}

Functions are introduced with func. Parameters carry an argument label used at the call site and a parameter name used in the body; writing _ as the label omits it. Parameters may have default values, may be variadic, and may be marked with a parameter modifier that changes how the argument is passed.

func constructGreeting(for name: String, punctuation: String = "!") -> String {
    "Hello \(name)\(punctuation)"        // Single-expression body: return is implicit.
}

constructGreeting(for: "Craig")           // "Hello Craig!"

// Parameter modifiers: inout, borrowing, consuming.
func normalise(_ value: inout Double)      { value = max(0, min(1, value)) }
func inspect(_ buffer: borrowing Data)     { /* read-only, no retain */ }
func take(_ buffer: consuming Data)        { /* ownership transferred in */ }

var v = 1.7
normalise(&v)                              // & marks the mutating argument at the call site.

The four ways an argument can be passed.

ModifierSemanticsEffect at the call siteTypical use
inoutCopy-in, copy-outCaller writes &; the value is written back on returnMutating a caller's variable in place
borrowingRead-only access, no ownership transferNo retain/release traffic for the callLarge values inspected but not stored
consumingOwnership moves into the calleeCaller may not use the value afterwardsSinks: initialisers, appends, noncopyable handoff
(default)Pass by value, callee borrowsCompiler chooses the cheapest legal strategyEverything else

Source: The Swift Programming Language, Declarations → Parameter Modifiers. Write code against the copy-in copy-out model; the call-by-reference optimisation is not observable in correct programs.

Exclusivity. Within a function you may not access a value that was also passed as an inout argument, and you may not pass the same value to two inout parameters. Overlapping access is diagnosed at compile time where it can be proved and trapped at runtime otherwise. See Memory management.
Any typeValue typesReference typesConstraintsstructenumtupleclassactorprotocolsome Pany P
Figure 4. Swift's nominal type kinds. The division is exhaustive and disjoint, which is what makes the value-versus-reference question answerable by looking at a declaration's keyword alone. Protocols sit apart because they are constraints on the other two, not a third kind of instance.

The type system

Swift's type system is static, strong, nominal, and inferred. Types are checked before execution; implicit conversions between distinct types do not exist, including between numeric types — Int and Int64 are separate types and must be converted explicitly. Conformance is nominal: a type participates in a protocol only by declaring conformance, in its definition or in an extension. Inference is local and bidirectional within an expression, so annotations are needed only where an expression is genuinely ambiguous or where a declaration is separated from its initialisation.

The six type-level declarations. Structures, enumerations, and actors are the ones a modern Swift codebase reaches for most; classes are needed for identity, inheritance, and Objective-C bridging.

KindSemanticsStorageDistinguishing capabilityWhen to reach for it
structValueStack or inline in its containerMemberwise init, no inheritanceDefault choice for data
enumValueTag plus payload, size of the largest caseAssociated and raw values, exhaustive matchingClosed sets of alternatives
classReferenceHeap, reference-countedSingle inheritance, deinit, identityShared mutable state, Objective-C interop
actorReferenceHeap, reference-countedIsolated state, implicit SendableConcurrency-safe shared state
protocolRequirements, extensions, associated typesAbstraction over unrelated types
typealiasNaming only, no new type identityReadability

Sources: Structures and Classes, Enumerations, and Actor Declaration chapters of The Swift Programming Language.

// Enumerations carry payloads, so states that cannot coexist cannot be represented together.
enum Download {
    case pending
    case running(bytesReceived: Int, total: Int?)
    case failed(any Error)
    case finished(URL)

    var progress: Double? {
        guard case .running(let received, let total?) = self else { return nil }
        return Double(received) / Double(total)
    }
}

// Structures get a memberwise initializer and value semantics for free.
struct Rectangle: Equatable, Sendable {
    var width: Double
    var height: Double
    var area: Double { width * height }
}

var a = Rectangle(width: 3, height: 4)
var b = a          // A copy, not a reference.
b.width = 10
print(a.width)     // 3 - a is unaffected.

Two type-erasing supertypes exist. Any can hold a value of any type at all; AnyObject holds any class instance and is the bridge to Objective-C's id. Both defer work to runtime and should be a last resort: they defeat specialisation, they force dynamic casts with is, as?, and as!, and they are unavailable in Embedded Swift.

Closures

Closures are self-contained blocks of functionality that can be stored, passed, and returned like any other value. A closure's type is written as its parameter and return types; where the compiler can infer them, the types, the parameter names, and the return keyword may all be omitted, leaving shorthand argument names $0, $1, and so on.

let explicit: (Int, Int) -> Int = { lhs, rhs in return lhs + rhs }
let inferred = { (lhs: Int, rhs: Int) -> Int in lhs + rhs }

let names = ["Josephine", "Steve", "Chris", "Barbara"]
let shortNames = names.filter { $0.count < 6 }        // ["Steve", "Chris"]
let lengths = names.map(\.count)                      // Key path as a function.

// Trailing closure syntax; parentheses drop when the closure is the only argument.
func foo(bar: () -> Int, baz: (Int) -> Int) -> Int { baz(bar()) }

foo(bar: { 1 }, baz: { $0 + 1 })      // No trailing closures.
foo(bar: { 1 }) { $0 + 1 }            // One trailing closure.
foo { 1 } baz: { $0 + 1 }             // Multiple trailing closures (5.3+).

Closures capture values from the enclosing scope by reference and keep them alive for as long as the closure lives. That is the mechanism behind the most common ARC leak in Swift, and the reason for capture lists: [weak self] or [unowned self] declares the capture non-owning. An escaping closure — one stored or used after the function returns — must be marked @escaping, which is what makes the lifetime question visible in the signature.

func makeMultiplier(withMultiple multiple: Int) -> (Int) -> Int {
    { $0 * multiple }                     // multiple is captured and outlives the call.
}

let triple = makeMultiplier(withMultiple: 3)
print(triple(10))                          // 30

final class Loader {
    var onFinish: (@Sendable () -> Void)?
    func start() {
        // Without [weak self] this closure would keep the Loader alive indefinitely.
        onFinish = { [weak self] in self?.cleanUp() }
    }
    func cleanUp() { }
}

Strings, Unicode, and regex

The standard library's String is a Unicode-correct collection of Character values, where a Character is an extended grapheme cluster — what a reader perceives as a single character, which may be several Unicode scalars. Because clusters vary in width, String is not randomly indexable by integer; it is indexed by String.Index, and count is an O(n) operation. This is a deliberate trade: it removes an entire class of silent corruption when slicing text containing combining marks, emoji, or non-Latin scripts.

let flag = "\u{1F1E9}\u{1F1EA}"   // Two scalars, one Character.
print(flag.count)                  // 1
print(flag.unicodeScalars.count)   // 2

// Views onto the same storage, none of them copies.
let s = "Cafe\u{301}"              // "Café" as e + combining acute
s.count            // 4  - characters (grapheme clusters)
s.unicodeScalars   // 5  - Unicode scalar values
s.utf8.count       // 6  - UTF-8 code units

// Interpolation is a protocol, so types can define how they appear.
var score = 980
print("Your score is \(score).")

// Raw strings suppress escape processing; the # count sets the delimiter.
let path = #"C:\Program Files\App\config.json"#
let regexLike = ##"Use \(x) literally"##

Swift 5.7 added regular expressions to the language, with both a literal syntax checked at compile time and a result-builder DSL for cases where a literal would be unreadable. Captures are typed: the compiler knows the arity and the types of the capture groups, so a match result is destructured without stringly-typed index lookups.

import RegexBuilder

let semver = /(\d+)\.(\d+)\.(\d+)/          // Literal; captures typed as (Substring, ...)
if let m = "6.3.3".wholeMatch(of: semver) {
    let (_, major, minor, patch) = m.output    // All Substring, checked at compile time.
    print(major, minor, patch)                 // 6 3 3
}

// The builder form, for expressions that would be write-only as a literal.
let field = Reference(Substring.self)
let header = Regex {
    Capture(as: field) { OneOrMore(.word) }
    ": "
    Capture { OneOrMore(.any) }
}

When Foundation is imported, String bridges to NSString on Apple platforms, so Objective-C APIs accept Swift strings without an explicit conversion.

Callable objects

A type becomes callable by defining callAsFunction. This is ordinary method dispatch with sugar at the call site, so overloading, argument labels, throws, and async all work as they do on any other method.

struct Scale {
    var factor: Int
    func callAsFunction(_ number: Int, offset: Int = 0) -> Int {
        factor * number + offset
    }
}

let double = Scale(factor: 2)
double(21)                       // 42
double.callAsFunction(21)        // 42 - identical
Package — packageModule — internal (the default)File — fileprivateprivateprivate(set)
Figure 5. Access levels are defined by containment, so the diagram is the specification. Each level names the outermost boundary a declaration may be seen from: open and public cross the module edge, package stops at the package, internal at the module, and fileprivate and private inside the file.

Access control

Swift has six access levels. Unlike most object-oriented languages, they are scoped to files, modules, and packages rather than to inheritance hierarchies: private means “this lexical scope”, not “not visible to subclasses”. The package level was added in Swift 6.0 for the common case of a multi-module package that needs to share code internally without publishing it as API.

Ordered from narrowest to widest. The default when nothing is written is internal.

LevelVisible fromScopeTypical use
privateThe enclosing declaration and its extensions in the same fileNarrowestImplementation detail of one type
fileprivateThe declaring source fileFileCooperating types in one file
internalThe declaring moduleModule (default)Everything not deliberately exported
packageEvery module built with the same -package-namePackage (6.0+)Shared across a multi-module package without becoming public API
publicAny importing module; not subclassable outsidePublicLibrary API
openAny importing module; subclassable and overridable outsideWidest, classes onlyDeliberately extensible class API

Source: Access Control chapter and Declarations → Access Control Levels. Two modules are in the same package when the build system passes them the same -package-name.

Each level also accepts a (set) argument, giving a setter narrower access than its getter — the idiomatic way to publish a read-only property whose value the type controls. A guiding principle constrains the whole system: no entity may be defined in terms of another entity with a narrower access level, so a public function cannot take an internal parameter type.

public struct Counter {
    public private(set) var value = 0     // Readable everywhere, writable only inside.
    package var debugLabel = ""           // Visible to sibling modules in this package.
    private var ticks: [Date] = []

    public mutating func increment() {
        value += 1
        ticks.append(.now)
    }
}

Optionals and chaining

Optionals are the feature that removes the null-pointer error as a category. Optional is an ordinary enumeration in the standard library with cases none and some(Wrapped); the trailing ? is sugar for it. Because String and String? are distinct types, a value that might be absent cannot be used as though it were present without the compiler saying so.

public enum Optional<Wrapped>: ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
}

var reading: Int? = nil          // Sugar for Optional<Int>.none
reading = 42                     // Optional<Int>.some(42)

Seven ways to get at the value inside an optional, and what each costs.

FormBehaviourFailure modeWhen to use
if let / guard letBinds the wrapped value in a new scopeNoneThe default; guard when the rest of the scope needs the value
??Supplies a fallback valueNoneA sensible default exists
?. (chaining)Propagates nil through a chain of callsNoneDeep access where absence anywhere means absence overall
map / flatMapTransforms the wrapped value if presentNoneFunctional pipelines over an optional
! (force unwrap)Traps if nilProcess terminatesAn invariant the type system cannot express; document why
try!, as!Same, for errors and castsProcess terminatesSame reasoning
Implicitly unwrapped (Int!)Unwraps automatically on useProcess terminatesTwo-phase initialisation and imported headers only

Source: The Basics → Optionals and the Optional Chaining chapter. Every trapping form is syntactically distinct, which is the point: the risky operation is visible in review.

// Chaining: one nil anywhere makes the whole expression nil, and the calls after it are skipped.
let leaseStart = building.tenantList[5].leaseDetails?.startDate      // Date?

// Shorthand binding (5.7+) when the names match.
if let leaseStart { print(leaseStart) }

// Optional chaining on a method call that itself returns an optional.
let firstInitial = person.name?.first.map(String.init) ?? "?"

// Force unwrap: acceptable only where the invariant is genuine and stated.
let bundled = Bundle.main.url(forResource: "schema", withExtension: "json")!
// ^ Ships in the bundle; a nil here is a build error, not a runtime condition.

Because unwrapping is an operation on a concrete wrapper type rather than a runtime dispatch mechanism, the compiler can use static dispatch throughout and can optimise away the wrapper entirely for pointer-sized payloads — an Optional<SomeClass> is represented as a possibly-null pointer with no extra storage.

Value types and copy-on-write

In most object-oriented languages an object lives on the heap and a handle to it is a pointer; passing the object copies the pointer, so every holder sees the same data. Basic types such as integers are instead represented directly and copied on assignment. Swift makes that choice explicit and per-type: class and actor give reference semantics, struct and enum give value semantics.

Value semantics would be prohibitively expensive for large aggregates if every assignment copied eagerly, so Array, Dictionary, Set, and String use copy-on-write: the buffer is shared until a mutation occurs on a value whose buffer is not uniquely referenced, at which point that value copies. The observable semantics are those of a full copy; the cost is deferred and often never paid.

var first = [1, 2, 3]
var second = first        // No allocation: both share one buffer.
second.append(4)          // Buffer is not uniquely referenced -> copies here.
print(first)              // [1, 2, 3] - unaffected.

// The same mechanism is available to your own types.
struct Image {
    private final class Storage { var pixels: [UInt8]; init(_ p: [UInt8]) { pixels = p } }
    private var storage: Storage

    init(pixels: [UInt8]) { storage = Storage(pixels) }

    var pixels: [UInt8] {
        get { storage.pixels }
        set {
            if !isKnownUniquelyReferenced(&storage) {   // The CoW test.
                storage = Storage(storage.pixels)
            }
            storage.pixels = newValue
        }
    }
}
Choosing between them. Reach for a struct unless you need one of the three things only a reference type provides: identity (two variables must refer to the same instance), inheritance, or a deinit. Concurrency sharpens the rule — value types with Sendable members cross isolation boundaries freely, while a mutable class does not.

Extensions

Extensions add functionality to an existing type without subclassing it and without access to its source. They can add computed properties, methods, initializers, subscripts, nested types, and protocol conformances — but not stored properties, because that would change the type's layout. Beyond retrofitting behaviour onto types you do not own, extensions are the standard way to organise a large type: the definition holds the stored properties, and each extension holds one protocol conformance or one coherent group of methods.

extension Rectangle {
    var isSquare: Bool { width == height }
    func scaled(by factor: Double) -> Rectangle {
        Rectangle(width: width * factor, height: height * factor)
    }
}

// Conditional conformance: Array is Equatable exactly when its Element is.
extension Array: Equatable where Element: Equatable { }

// Constrained extension: these members exist only for the matching Element type.
extension Collection where Element: Numeric {
    var total: Element { reduce(.zero, +) }
}

// Retroactive conformance to your own protocol, on a type you do not own.
extension URL: @retroactive Identifiable {
    public var id: String { absoluteString }
}

Protocol-oriented programming

A protocol states that a conforming type provides a set of properties, methods, initializers, subscripts, and associated types. Protocols are used where other object-oriented languages would use multiple inheritance, but the feature sets are not equivalent. In Objective-C and most languages with an interface concept, each conforming type must implement every requirement itself. Swift adds two things that change the character of the feature: protocol extensions, which supply default implementations, and generics, which let those defaults be written once against constrained type parameters.

protocol Drawable {
    associatedtype Canvas
    var boundingBox: Rectangle { get }
    func draw(into canvas: inout Canvas)
}

// A protocol extension supplies behaviour to every conforming type at once.
extension Drawable {
    var aspectRatio: Double { boundingBox.width / boundingBox.height }
    func drawTwice(into canvas: inout Canvas) {
        draw(into: &canvas)
        draw(into: &canvas)
    }
}

// Protocol inheritance and composition.
protocol Named { var name: String { get } }
protocol Aged  { var age: Int { get } }

func greet(_ subject: some Named & Aged) {
    print("\(subject.name) is \(subject.age)")
}

Dispatch has a subtlety worth knowing. A requirement declared in the protocol body is dispatched dynamically through the protocol witness table, so a conforming type's override is found. A member declared only in a protocol extension is dispatched statically on the static type of the expression, so a conforming type that defines a member of the same name shadows rather than overrides it. Declaring the member in the protocol body as well as the extension is what makes it customisable.

Swift 6.2 added the ability to suppress implicit conformances and to state common conformances as constraints, which matters for types where the compiler would otherwise infer Sendable or Copyable and thereby make a promise the type cannot keep.

Generics, opaque types, and existentials

Generics let one definition serve many concrete types with no loss of type information and, after specialisation, no dispatch cost. Constraints are expressed with a conformance requirement after the type parameter or in a where clause, which can also constrain associated types several levels deep.

func allEqual<C: Collection>(_ c: C) -> Bool where C.Element: Equatable {
    guard let first = c.first else { return true }
    return c.allSatisfy { $0 == first }
}

// Generic type with a constrained extension and a generic subscript.
struct Stack<Element> {
    private var storage: [Element] = []
    mutating func push(_ e: Element) { storage.append(e) }
    mutating func pop() -> Element? { storage.popLast() }
}

extension Stack: Equatable where Element: Equatable { }

// Parameter packs (5.9+): variadic generics over heterogeneous types.
func zipAll<each T>(_ values: repeat each T) -> (repeat each T) {
    (repeat each values)
}

Where a function needs to hide its concrete return type without giving up type identity, Swift offers opaque types. some P means “one specific type conforming to P, chosen by the callee and fixed for all callers”. Its counterpart, any P, is an existential: a box that may hold a different conforming type on each assignment. The distinction is the most common source of confusion in modern Swift, and it is a performance decision as much as a typing one.

some P against any P, the decision every protocol-using API faces.

PropertyOpaque typeExistential
Spellingsome Pany P
Type identityOne fixed underlying type, hidden from the callerVaries per value; erased
StorageSame size as the underlying typeBoxed if larger than the inline buffer
DispatchStatic; specialisableDynamic, through the witness table
Associated typesUsable and constrainable (some Collection<Int>)Only through primary associated types (5.7+)
Heterogeneous collectionsNot possibleThe reason to use it
Self requirementsAllowedRestricted
Default choiceYes — prefer itWhen values of differing types must share one variable

Source: Opaque and Boxed Protocol Types chapter. Since Swift 6.1, some may also be written in parameter position as lightweight generic syntax.

// Opaque: the caller cannot name the type, but the compiler knows it is always the same.
func makeShape() -> some Drawable { Rectangle(width: 1, height: 1) }

// Existential: each element may be a different concrete type.
var scene: [any Drawable] = [circle, rectangle, path]

// Lightweight generic syntax; these two declarations are equivalent.
func render(_ shape: some Drawable) { }
func render<S: Drawable>(_ shape: S) { }

// Primary associated types (5.7+) let an existential keep its element type.
func sum(_ xs: any Collection<Int>) -> Int { xs.reduce(0, +) }

Error handling and typed throws

Swift has no exceptions in the C++ or Java sense. A throwing function is marked throws in its signature, every call to one is marked try at the call site, and errors propagate only along that marked path — there is no non-local unwinding through unmarked frames. Errors are ordinary values conforming to the empty Error protocol, so an enumeration with associated values is the usual representation.

enum MathError: Error {
    case divisionByZero
    case missingOperand
    case outOfRange(value: Int, allowed: ClosedRange<Int>)
}

func evaluate(_ input: String) throws -> Int { /* ... */ }

do {
    let result = try evaluate("6 / 0")
    print(result)
} catch MathError.divisionByZero {
    print("Undefined.")
} catch let MathError.outOfRange(value, allowed) {
    print("\(value) is outside \(allowed).")
} catch {
    print("Unexpected: \(error)")     // `error` is bound implicitly.
}

Six related forms. Typed throws is the significant recent addition: an untyped throws is throws(any Error), which forces existential boxing that embedded targets cannot afford.

FormBehaviourEffect on the callerWhen to use
tryPropagates the error to the callerCaller must handle or rethrowThe default
try?Converts the result to an optionalError value is discardedThe error carries no information you will use
try!Traps on errorProcess terminatesA failure here is a programming error
throws(E)Declares the exact error type (6.0+)Caller can switch exhaustivelyClosed error sets; embedded and performance-sensitive code
rethrowsThrows only if a closure argument throwsNon-throwing callers stay non-throwingHigher-order functions such as map
deferRuns on every exit from the scopeReverse order of declarationCleanup that must happen on both the success and failure path

Source: Error Handling chapter, including Specifying the Error Type, added in Swift 6.0.

// Typed throws (6.0+): the error type is part of the signature.
func parse(_ text: String) throws(MathError) -> Int {
    guard let n = Int(text) else { throw .missingOperand }
    return n
}

do {
    _ = try parse("x")
} catch {
    // `error` is a MathError here, not `any Error`, so this switch is exhaustive.
    switch error {
    case .divisionByZero, .missingOperand: break
    case .outOfRange: break
    }
}

// defer runs on every path out of the scope, in reverse order of declaration.
func withTemporaryFile<T>(_ body: (URL) throws -> T) throws -> T {
    let url = makeTemporaryFile()
    defer { try? FileManager.default.removeItem(at: url) }
    return try body(url)
}

Property wrappers and result builders

Two features exist mainly to let libraries define declarative syntax without compiler changes, and together they are what makes SwiftUI possible as a library rather than a language extension.

A property wrapper factors out an access pattern — validation, storage, laziness, observation — into a type applied with @. The wrapper's wrappedValue is what the property appears to be; its optional projectedValue, reached with $, exposes the wrapper's own API.

@propertyWrapper
struct Clamped<Value: Comparable> {
    private var value: Value
    private let range: ClosedRange<Value>

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }

    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
    var projectedValue: ClosedRange<Value> { range }
}

struct Mixer {
    @Clamped(0...1) var gain: Double = 0.5
}

var m = Mixer()
m.gain = 4.2
print(m.gain, m.$gain)     // 1.0 0.0...1.0

A result builder transforms a sequence of statements in a closure into a single value by calling the builder's buildBlock, buildOptional, buildEither, buildArray, and related methods. That is how a SwiftUI body or a RegexBuilder pattern reads as a list of declarations while producing one strongly typed value.

@resultBuilder
enum HTMLBuilder {
    static func buildBlock(_ parts: String...) -> String { parts.joined() }
    static func buildOptional(_ part: String?) -> String { part ?? "" }
    static func buildEither(first: String) -> String { first }
    static func buildEither(second: String) -> String { second }
    static func buildArray(_ parts: [String]) -> String { parts.joined() }
}

func page(@HTMLBuilder _ content: () -> String) -> String { "<html>\(content())</html>" }

let markup = page {
    "<h1>Report</h1>"
    if includeSummary { "<p>Summary</p>" }
    for row in rows { "<li>\(row)</li>" }
}
Use site#stringify(x + y)syntaxCompilerserialises the syntax treesyntaxPlugin processSwiftSyntax, sandboxedsyntaxExpansioninserted, then type-checked
Figure 6. Macro expansion as a data flow. The plugin is a separate, sandboxed process that receives a syntax tree and returns syntax — it never sees types and cannot reach the file system or the network. The expansion is then type-checked like any other source, so a macro cannot introduce an unchecked construct.

Macros

Macros, added in Swift 5.9, generate code at compile time. Unlike C preprocessor macros they operate on the parsed syntax tree rather than on text, they are type-checked both before and after expansion, and they cannot see or modify anything outside the syntax they are given. A macro is declared in one module and implemented in another, as a separate compiler plugin process built with SwiftSyntax.

The seven macro roles. Peer, member, and accessor roles must declare the names they introduce, using named(_:), prefixed(_:), suffixed(_:), overloaded, or arbitrary.

RoleKindWhat it generatesExample use
@freestanding(expression)FreestandingProduces a value or a compile-time diagnostic#warning, #externalMacro
@freestanding(declaration)FreestandingProduces one or more declarationsGenerating boilerplate types
@attached(peer)AttachedAdds declarations beside the one it is attached toA completion-handler twin of an async method
@attached(member)AttachedAdds members to the type or extension@OptionSet adding init(rawValue:)
@attached(memberAttribute)AttachedAdds attributes to the members of a typeMarking every stored property observable
@attached(accessor)AttachedAdds accessors, turning a stored property into a computed one@Observable property tracking
@attached(extension)AttachedAdds a conformance, where clause, or membersSynthesising a protocol conformance

Source: Macros chapter and the @attached / @freestanding attribute reference. A macro may declare several roles by repeating the attribute.

// Declaration - in the library that vends the macro.
@attached(member, names: named(RawValue), named(rawValue), named(init), arbitrary)
@attached(extension, conformances: OptionSet)
public macro OptionSet<RawType>() =
        #externalMacro(module: "SwiftMacros", type: "OptionSetMacro")

// Implementation - in a compiler plugin target, using SwiftSyntax.
import SwiftSyntax
import SwiftSyntaxMacros

public struct OptionSetMacro: MemberMacro, ExtensionMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingMembersOf decl: some DeclGroupSyntax,
        in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
        // Inspect `decl`, emit diagnostics through `context`, return new members.
        ["typealias RawValue = Int", "var rawValue: RawValue"]
    }
}

Expansion is a four-step pipeline: the compiler parses the source into a syntax tree, sends the relevant subtree to the plugin process, replaces the macro call with the returned syntax, and then type-checks and compiles the result. Because the plugin runs as a separate process, a macro cannot crash the compiler, and swift build asks for explicit trust before running a plugin from a dependency. Expanded code is inspectable — in Xcode through “Expand Macro”, and on the command line by dumping the expansion — which matters because a macro that generates wrong code otherwise produces errors pointing at source the developer never wrote.

Cost. Macro plugins are built and run during every compilation that uses them, and SwiftSyntax is a large dependency. On a large target, adopting a macro-heavy library is a measurable build-time decision, not only an API one.

Ownership, borrowing, and noncopyable types

Swift's default model is that values are copyable and the compiler inserts retains, releases, and copies wherever it must. Since Swift 5.9, that default can be opted out of, giving a discipline close to Rust's for the cases that need it: unique resources such as file descriptors, locks, and hardware registers, where an accidental copy is a bug rather than an inefficiency.

Suppressing Copyable with ~Copyable makes a type noncopyable: it has exactly one owner, it is moved rather than copied on assignment, and its deinit runs deterministically when the owner's lifetime ends. Swift 6.0 extended this through generics, so a generic parameter may be declared <T: ~Copyable>.

struct FileDescriptor: ~Copyable {
    private let fd: Int32

    init(opening path: String) throws { fd = try openFile(path) }

    borrowing func read(into buffer: inout [UInt8]) throws { /* ... */ }
    consuming func close() { closeFile(fd) }          // Consumes self; no double close.

    deinit { closeFile(fd) }                          // Runs if close() was never called.
}

func process(_ path: String) throws {
    let file = try FileDescriptor(opening: path)
    var buffer = [UInt8](repeating: 0, count: 4096)
    try file.read(into: &buffer)                      // Borrow: file is still usable.
    file.close()                                      // Consume: file is gone after this.
    // try file.read(into: &buffer)                   // Error: used after consume.
}

// Explicit lifetime control on ordinary copyable values.
let big = makeLargeArray()
let moved = consume big        // `big` may not be used after this point.

Three related facilities complete the picture. consume ends a binding's lifetime explicitly so the value is moved rather than retained. borrowing and consuming parameter modifiers state the convention at a function boundary. And @inline(__always) plus the Span and InlineArray types added in recent releases give bounds-checked access to contiguous memory without the reference counting that Array implies.

Back to top

Concurrency and data-race safety

Swift 5.5 introduced structured concurrency; Swift 6 made its safety guarantees mandatory. The model has two halves that are worth separating. The first is structured concurrency: a way of expressing asynchronous work whose lifetime is bounded by the enclosing scope. The second is data isolation: a compile-time proof that no two pieces of code mutate the same memory concurrently. The first is convenience; the second is the part that makes Swift unusual.

Asynchronous functions

An async function may suspend. Every call to one is marked await, which makes each potential suspension point visible in the source — important because state can change across a suspension. Unlike a callback-based API, an async function returns its value or throws its error directly, so ordinary control flow, defer, and try all work.

func downloadPhoto(named name: String) async throws -> Photo { /* ... */ }

func loadGallery(names: [String]) async throws -> [Photo] {
    var photos: [Photo] = []
    for name in names {
        photos.append(try await downloadPhoto(named: name))   // Sequential.
    }
    return photos
}

// async let runs the calls concurrently; await gathers them.
func loadThree() async throws -> [Photo] {
    async let a = downloadPhoto(named: "a")
    async let b = downloadPhoto(named: "b")
    async let c = downloadPhoto(named: "c")
    return try await [a, b, c]
}

// AsyncSequence: for-await-in over values produced over time.
for try await line in fileHandle.bytes.lines {
    print(line)
}
SuspendedRunningCancelledresumed by the executorcancel() — sets a flagcheckCancellation() throws, then unwinds
Figure 7. A task's lifecycle. Cancellation is the transition worth reading twice: cancel() sets a flag and returns immediately, so a task leaves the cancelled state only when it next checks. A task that never checks runs to completion after being cancelled.

Tasks, task groups, and cancellation

A task is the unit of asynchronous execution. Tasks form a tree: a child task created inside a task group or by async let cannot outlive its parent, which is what “structured” means and what guarantees that cancellation and error propagation reach every descendant. An unstructured Task { } escapes that discipline and must be managed by hand.

// Structured: the group cannot return until every child has finished or been cancelled.
func loadAll(names: [String]) async throws -> [String: Photo] {
    try await withThrowingTaskGroup(of: (String, Photo).self) { group in
        for name in names {
            group.addTask { (name, try await downloadPhoto(named: name)) }
        }
        var result: [String: Photo] = [:]
        for try await (name, photo) in group { result[name] = photo }
        return result
    }
}

// Unstructured: outlives the calling scope; you hold the handle and the responsibility.
let handle = Task { try await downloadPhoto(named: "hero") }
let photo = try await handle.value
handle.cancel()

Cancellation in Swift is cooperative: cancelling a task sets a flag on it and on all of its descendants, and it is the task's own code that must notice. Library suspension points such as Task.sleep throw CancellationError, but a long computation must poll Task.isCancelled or call Task.checkCancellation() itself. Work that does not check is not cancellable, however many times cancel() is called.

func renderFrames(_ frames: [Frame]) async throws -> [Image] {
    var output: [Image] = []
    for frame in frames {
        try Task.checkCancellation()      // Throws CancellationError if cancelled.
        output.append(render(frame))      // Synchronous, CPU-bound work.
        await Task.yield()                // Give the executor a chance to run others.
    }
    return output
}

Data isolation

Swift's guarantee is that whenever code reads or modifies a piece of data, no other code is modifying it concurrently. It reaches that guarantee by proving each piece of mutable state belongs to exactly one isolation domain, and by checking every value that crosses a domain boundary.

@MainActor UI state var rows: [Row] serialised on one thread actor Store private state var cache: [Key: V] one turn at a time Task task-local state var partial: Int reachable by one task await await Boundary check only Sendable values may cross — or a non-Sendable value sent exactly once anything else is a compile-time error under the Swift 6 language mode
Figure 8. The three kinds of isolation domain and the boundaries between them. A crossing is always visible in the source: await marks the suspension, and the compiler checks that the value being passed is safe to share. Diagram: Programming Language Atlas.

Five isolation domains. Every mutable value in a Swift 6 program belongs to one of them, or the compiler reports it.

DomainIsolationTypical declarationWhy it is safe
Immutable valueAlways isolatedA let of a Sendable typeNothing can mutate it, so nothing can race
Task-local stateIsolated to the current taskA local var inside a functionNo other code holds a reference to that memory
Actor-isolated stateIsolated to one actorA stored property of an actorAccess from outside requires await and takes a turn
Global-actor-isolatedIsolated to a shared actor@MainActor varAll access serialised on that actor, across the whole program
nonisolatedNot isolatedA pure function on an actorCallable synchronously from anywhere; may not touch mutable isolated state

Source: Concurrency → Isolation, and the Data Race Safety chapter of the Swift 6 migration guide.

The main actor and global actors

The main actor is the most important actor in most programs: it protects everything used to draw the user interface, and running on it means running on the main thread. @MainActor can be applied to a function, a closure, a property, or a whole type; framework protocols such as SwiftUI's View are already marked, so conforming types inherit the isolation without writing it. A global actor is the general form — a singleton actor that any number of declarations across the program can be isolated to.

@MainActor
func show(_ photo: Photo) { /* UI code */ }

func downloadAndShow(named name: String) async throws {
    let photo = try await downloadPhoto(named: name)   // Off the main actor.
    await show(photo)                                  // Hops to the main actor.
}

// Fine-grained: only the members that touch the UI are isolated.
struct PhotoGallery {
    @MainActor var photoNames: [String] = []
    var hasCachedPhotos = false

    @MainActor func drawUI() { }
    func cachePhotos() async { }        // Networking; not main-actor isolated.
}

// A custom global actor for a subsystem with its own serial domain.
@globalActor
actor DatabaseActor {
    static let shared = DatabaseActor()
}

@DatabaseActor func migrate() { /* serialised against all other @DatabaseActor work */ }
Caller Aactor StoreCaller Bawait store.load(k)suspends — actor releasedawait store.clear()resumes — cache now empty
Figure 9. Actor reentrancy on a time axis. The actor is released at the suspension point, so a second caller may enter and mutate state before the first resumes. Any invariant read before an await must be re-established after it.

Actors

An actor is a reference type that allows only one task at a time to touch its mutable state. Code inside the actor accesses its own properties synchronously; code outside must await, which is where the turn-taking happens. The guarantee is not merely that accesses are serialised but that they are serialised at a known granularity: an actor method with no await inside it runs to completion without interleaving, so invariants that are temporarily broken mid-method are never observed.

actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }

    // No await inside: runs atomically with respect to other tasks on this actor.
    func update(with measurement: Int) {
        measurements.append(measurement)     // Invariant temporarily broken here...
        if measurement > max { max = measurement }   // ...restored here.
    }
}

let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
print(await logger.max)     // await: this access takes a turn.
// print(logger.max)        // Error: actor-isolated property accessed synchronously.
Reentrancy. Actors are reentrant: an await inside an actor method releases the actor, so another task may run a method on the same actor before the first resumes. State read before an await may therefore be stale after it. Actors prevent data races, not logical races — re-check invariants after every suspension point.

Sendable and crossing boundaries

Sendable marks a type whose values can be transferred between isolation domains safely. It is a marker protocol with no requirements: the compiler checks the type's structure rather than calling anything. Value types composed entirely of Sendable members conform implicitly; a final class with only immutable Sendable storage may conform; a mutable class may not, unless the author takes responsibility with @unchecked Sendable and an external lock.

struct Reading: Sendable {            // Implicit for a frozen struct of Sendable members.
    let value: Double
    let timestamp: Date
}

final class Cache: @unchecked Sendable {   // Author asserts safety; compiler stops checking.
    private let lock = NSLock()
    private var storage: [String: Data] = [:]
    func value(for key: String) -> Data? {
        lock.withLock { storage[key] }
    }
}

// `sending` transfers a non-Sendable value across a boundary by proving the
// caller gives up all remaining references to it.
func handOff(_ buffer: sending Buffer) async { await consumer.store(buffer) }

Migrating to the Swift 6 language mode

Enabling full checking on an existing module commonly produces hundreds of diagnostics, which is why the Swift project publishes a migration strategy rather than a switch. The recommended order is: pick one module, starting with the outermost one that nothing else depends on; enable individual upcoming feature flags while remaining in the Swift 5 mode so that issues surface as warnings and the build keeps working; address them; and only then move that module to the Swift 6 mode.

The guiding principle for the fixes themselves is to express what is already true rather than to refactor. If a type is in practice only ever used on the main actor, say so with @MainActor; do not redesign it. Unsafe opt-outs applied during migration then form the to-do list for real refactoring afterwards.

The six diagnostics that account for most of the work in a migration.

Diagnostic categoryWhat the compiler foundUsual resolution
Unsafe global or static variableA mutable static var is reachable from every domainMake it a let, isolate it to a global actor, or move it into an actor
Protocol conformance isolation mismatchA non-isolated protocol requirement implemented by an isolated typeIsolate the protocol, mark the requirement nonisolated, or use assumeIsolated
Non-Sendable type crossing a boundaryA class or closure captured by a task in another domainMake the type Sendable, pass a sending value, or copy out the data you need
Missing annotations in a dependencyA library not yet audited for concurrency@preconcurrency import, which downgrades its diagnostics to warnings
Non-isolated deinitializationdeinit runs in no particular domainDo not touch isolated state from deinit; capture what you need beforehand
Unmarked Sendable closureA callback whose concurrency contract is not in its typeAdd @Sendable to the closure type, or enable InferSendableFromCaptures

Source: Migrating to Swift 6, Common Compiler Errors and Incremental Adoption chapters.

Interoperating with the old world. DispatchSerialQueue can be adopted as an actor's executor, so an existing queue-based subsystem keeps its serialisation guarantees while presenting an actor interface. Callback-based C and Objective-C APIs are wrapped with withCheckedThrowingContinuation, which bridges a completion handler into an async function and traps if the continuation is resumed zero or more than one time.
func loadSettings() async throws -> Settings {
    try await withCheckedThrowingContinuation { continuation in
        legacyLoadSettings { settings, error in     // Callback-based C API.
            if let error { continuation.resume(throwing: error) }
            else { continuation.resume(returning: settings!) }
        }
    }
}

Back to top

Memory management

Swift manages memory with Automatic Reference Counting. Every class instance, closure context, and boxed existential carries a reference count; when the count reaches zero the instance is deallocated immediately and its deinit runs. There is no tracing collector, so destruction is deterministic and there are no collection pauses — the property that makes Swift usable for audio, embedded, and latency-sensitive server work. The cost is paid instead as retain and release traffic on every copy of a reference, and as the one failure mode counting cannot solve: cycles.

A strong reference cycle occurs when two instances hold strong references to each other; neither count ever reaches zero and neither is freed. weak and unowned break the cycle by declaring a reference non-owning. A weak reference must be an optional var, because it becomes nil when the referent is deallocated; an unowned reference is non-optional and traps if used after deallocation, so it is correct only where the referent is guaranteed to outlive the reference.

class Person {
    let name: String
    weak var home: Home?          // Weak: breaks the cycle, becomes nil automatically.

    init(name: String) { self.name = name }
    deinit { print("De-initialized \(name)") }
}

class Home {
    let address: String
    var owner: Person?

    init(address: String, owner: Person?) {
        self.address = address
        self.owner = owner
    }
    deinit { print("De-initialized \(address)") }
}

var stacy: Person? = Person(name: "Stacy")
var house21b: Home? = Home(address: "21b Baker Street", owner: stacy)
stacy?.home = house21b        // The two now reference each other.

stacy = nil                   // Count stays at 1: house21b still holds a strong reference.
house21b = nil                // Drops to 0, which drops stacy's count to 0.

// Prints:
// De-initialized 21b Baker Street
// De-initialized Stacy

Four reference strengths.

Reference kindIncrements the countType requirementAfter deallocationWhen to use
strong (default)YesNon-optional or optionalOwnership; the normal case
weakNoOptional var onlyBecomes nilBack-references, delegates, parent pointers
unownedNoNon-optionalTraps on use after deallocationReferent provably outlives the reference
unowned(unsafe)NoNon-optionalUndefined behaviour on use after deallocationPerformance-critical code that has proved the lifetime

Source: Automatic Reference Counting chapter and Declarations → Declaration Modifiers. unowned(safe) is the explicit spelling of the default unowned behaviour.

Closures create cycles just as readily as objects do, by capturing self strongly. A capture list declares the capture weak or unowned:

final class Downloader {
    var onProgress: ((Double) -> Void)?

    func begin() {
        // Without [weak self] the closure retains the Downloader, which retains the closure.
        onProgress = { [weak self] fraction in
            guard let self else { return }
            self.update(fraction)
        }
    }
    func update(_ fraction: Double) { }
}

Exclusive access to memory

Separately from reference counting, Swift enforces exclusive access: a write access to a variable may not overlap any other access to it. Most violations are caught at compile time; those that depend on runtime values are trapped by a check that is enabled in debug builds and, for class properties and globals, in release builds too. This is what makes inout safe, and it is the same property the concurrency model generalises across threads.

var stepSize = 1

func increment(_ number: inout Int) {
    number += stepSize          // Reads the global while it is being written as `number`.
}

increment(&stepSize)            // Runtime exclusivity violation: overlapping access.

// The fix is to make the second access a copy.
var copy = stepSize
increment(&copy)
stepSize = copy

Unsafe APIs

Every escape hatch is named for what it is. UnsafePointer, UnsafeMutableRawBufferPointer, unsafeBitCast, and unsafeDowncast give C-level access with no bounds or lifetime checking; a pointer obtained inside withUnsafeBytes is valid only for the duration of that closure. Newer bounds-checked alternatives — Span and RawSpan — cover most of the cases that previously required a raw pointer, with the lifetime tied to the owner by the compiler rather than by convention.

For a comparison with tracing collection and the other memory models in this atlas, see Reference cycles.

Back to top

Runtime, libraries, and interoperability

On Apple platforms Swift shares the Objective-C runtime, so C, Objective-C, C++, and Swift code can run in one process and one binary. Swift can subclass, extend, and call Objective-C types with near-complete access to the object model; the converse is limited — a Swift class cannot be subclassed in Objective-C, and Swift-only features such as generics, non-object optionals, enums with associated values, and Unicode identifiers cannot be expressed in the generated header at all.

Xcode maintains the bridge in both directions automatically: a bridging header exposes chosen Objective-C declarations to Swift, and a generated -Swift.h header exposes @objc-compatible Swift declarations back to Objective-C.

What Swift can talk to, and on what terms.

LanguageCallable from SwiftCallable from that languageHow it is enabledNotes
CYes, directYes, via @_cdeclAutomatic module map or a bridging headerPointers map to UnsafePointer family; no ownership inference
Objective-CYes, near-complete@objc members onlyBridging header, generated headerNullability audits drive optionality; Apple platforms only
C++Yes, since 5.9Yes, since 5.9-cxx-interoperability-mode=defaultC++ structs and classes import as value types; iterators are unsafe in Swift
JavaThrough swift-javaThrough swift-javaJNI-based, community projectUsed by the Android workgroup
PythonThrough PythonKitDynamic member lookupUses @dynamicMemberLookup and @dynamicCallable

Sources: Using C++ from Swift on Swift.org and the Objective-C interoperability documentation. C++ interoperability has documented source-stability guarantees for mixed-language codebases.

C++ interoperability, added in Swift 5.9, is the most substantial recent addition. The mapping is semantic rather than syntactic: a C++ struct or class imports as a Swift value type by default, with its copy constructor and destructor becoming Swift's copy and destroy operations; a const member function imports as nonmutating. Types with reference semantics must be annotated as immortal, shared, or unsafe reference types so that Swift knows how to manage their lifetime. Standard-library containers such as std::vector and std::map conform to Swift's Collection protocols where the conformance can be made safe; C++ iterators deliberately do not bridge, because their lifetime rules cannot be checked from Swift.

// C++ header, exposed through a Clang module.
struct Point { double x, y; double magnitude() const; };

// Swift: Point is a value type; magnitude() is nonmutating.
import Geometry

var p = Point(x: 3, y: 4)
print(p.magnitude())               // 5.0
let q = p                          // Copy constructor runs here.

// Swift exposed back to C++ via the generated header.
// #include "MyModule-Swift.h"

On non-Apple platforms Swift depends on none of this. The corelibs projects reimplement the Apple frameworks the standard library and package ecosystem assume: swift-corelibs-foundation, swift-corelibs-libdispatch, and swift-corelibs-xctest. Since 2024 a rewritten, Swift-native swift-foundation has been replacing the Objective-C-derived implementation on all platforms, which has removed a long-standing source of behavioural differences between macOS and Linux.

The standard library itself is written in Swift, but sits on a C++ runtime that implements the dynamic parts of the language: type metadata, generic instantiation, dynamic casting, reflection, and reference counting. That split is why Embedded Swift — which omits the metadata-dependent features — can produce firmware-sized binaries from the same source language.

Back to top

Debugging and diagnostics

Swift is designed to be debugged in the same environment it is built in. The compiler ships a read–eval–print loop, invoked as swift with no arguments, which gives the language interactive properties closer to Python than to traditional systems languages. Playgrounds extend this to a document format that mixes Markdown prose with code whose results are displayed as they are computed.

The debugger is LLDB, which embeds the Swift compiler itself so that expressions typed at a breakpoint are compiled with the same type checker as the program. That is why po can call generic functions and evaluate protocol-constrained expressions in context, and also why it needs the module's .swiftmodule to be present.

Runtime checks and instrumentation. Note the split: assertions vanish in release builds, preconditions do not.

FacilitySpellingActive inWhat it catches
Assertionsassert, assertionFailureDebug builds onlyInternal invariant you expect to hold
Preconditionsprecondition, preconditionFailureDebug and releaseA condition callers must satisfy
Fatal errorfatalErrorAll builds, always trapsUnreachable code, unimplemented paths
Address sanitizer-sanitize=addressOpt-in buildBuffer overruns in unsafe code and C interop
Thread sanitizer-sanitize=threadOpt-in buildRaces in code not yet under Swift 6 checking
Undefined-behaviour sanitizer-sanitize=undefinedOpt-in buildC and C++ interop layers
Malloc / leaks instrumentationInstruments, heap, leaksRuntime toolsRetain cycles that escaped review

Sources: The Basics → Assertions and Preconditions, and the Swift compiler documentation on sanitizers.

Two properties of the language make failures cheaper to diagnose than in C-family predecessors. Array bounds and integer overflow are checked rather than undefined, so the process traps at the point of error with a stack trace rather than corrupting memory and failing later. And optionality is in the type system, so the class of crash that dominates Objective-C and Java post-mortems — a null dereference far from its cause — is largely absent.

Back to top

Comparisons to other languages

Swift is a C-family language and shares much of C's surface: the operator set, curly-brace grouping, = for assignment and == for comparison, and square brackets for arrays. It adds an identity operator === for reference comparison, extends switch to pattern matching over any type, and changes the arithmetic operators to trap on overflow, with &+, &-, &* retaining the C wrapping behaviour where it is wanted.

Features removed from the C family

Several constructs that are easy to misuse were removed deliberately:

Differences from Objective-C

No header files; type inference; generics; first-class functions; enumerations with associated data; definable and overloadable operators; full Unicode in identifiers and operators; and no exceptions — Swift 2 replaced the Objective-C error model with the throws mechanism described above.

Swift against the three languages it is most often weighed against.

DimensionSwiftRustKotlinJava
Memory managementARC, deterministicOwnership and borrowing, compile-timeTracing GCTracing GC
Null safetyOptional in the type systemOption in the type systemNullable types, compiler-checkedAnnotations, partly checked
Error modelthrows with marked call sites; typed throwsResult and ?Unchecked exceptionsChecked and unchecked exceptions
Data-race safetyCompiler-enforced in Swift 6 modeCompiler-enforced via Send/SyncNot enforcedNot enforced
Concurrency primitivesasync/await, actors, task groupsasync/await, threads, channelsCoroutines, structured concurrencyVirtual threads, executors
GenericsReified, specialisable, with associated typesReified, monomorphised, with traitsReified on the JVM by the compilerErased
CompilationAOT to native via SIL and LLVMAOT to native via MIR and LLVMBytecode + JIT, or nativeBytecode + JIT
Interop reachC, Objective-C, C++C, and C++ through bindingsJava, Objective-C, JavaScriptC via JNI/FFM

Sources: the respective language references. Rows describe defaults in each language's current stable release; several are configurable.

For the same dimensions applied across a wider set of languages, see the feature matrix; for where Swift sits among memory models, see Memory management models.

PitchProposalReviewDecisionImplementation
Figure 10. The Swift Evolution cycle. Every language change in the version table above entered through it: a pitch on the forums, a written proposal, a public review with a stated window, a decision recorded by the responsible steering group, and an implementation that ships behind an upcoming-feature flag before it becomes a mode default.

Back to top

Development and other implementations

Because Swift runs on Linux, it is used as a server-side language. The Swift Server workgroup coordinates that ecosystem; the actively maintained web frameworks are Vapor and Hummingbird, both built on SwiftNIO, an event-driven non-blocking network application framework. IBM's Kitura, an early entrant, is discontinued. The arguments made for Swift on the server are the ones that follow from ARC: a memory footprint measured in megabytes, start-up in milliseconds with no JIT warm-up — which matters for serverless functions and for services rescheduled onto new containers — and no collector competing with the application for CPU.

Two compilation modes extend the language's reach without changing it. Embedded Swift is a subset compilation mode that drops reflection, existentials of unbounded layout, and runtime type metadata, producing binaries small enough for ARM Cortex-M and RISC-V microcontrollers. The Static Linux SDK links against Musl to produce a single statically linked executable with no shared-library dependencies, which is the usual form for a container image.

A second independent implementation, targeting Cocoa, the .NET Common Language Infrastructure, and the Java and Android platforms, exists as the “Silver” front end of the Elements Compiler from RemObjects Software. Subsets of Swift have been ported to Arduino and to Mac OS 9.

The language itself evolves through the public Swift Evolution process: a pitch on the Swift forums, a written proposal with a SE-NNNN number, a scheduled review period, and a decision by the language steering group with published rationale. Accepted proposals that would change existing behaviour ship first as upcoming feature flags before becoming the default in a later language mode.

Back to top

References and sources

  • Apple Inc. and the Swift project — The Swift Programming Language (6.4 beta edition), docs.swift.org/swift-book. The normative language guide and reference, including the Language Reference grammar and the Document Revision History used for the feature timeline above.
  • Swift.org — documentation index, platform support pages, Using C++ from Swift, API Design Guidelines, the Server and Standard Library pages, and release announcements.
  • Swift.org — Migrating to Swift 6, the concurrency migration guide covering data-race safety, migration strategy, common compiler errors, and incremental adoption.
  • Apple Developer — developer.apple.com/documentation/swift, the standard library API reference, including the operator and precedence-group declarations.
  • swiftlang/swift on GitHub — release tags and dates used in the version history table from 6.0 onward.
  • Swift Evolution — proposal index for the dating of individual language features.
  • Stack Overflow Developer Survey, 2015 and 2016, for the popularity figures cited under History.

The historical narrative and version table for releases through 5.10 are adapted from the Wikipedia article “Swift (programming language)”, available under the Creative Commons Attribution-ShareAlike 4.0 licence; citation markers from that article have been replaced by the consolidated source list above. Code examples are original or adapted from the cited official documentation.

Back to top