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.
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 mode | Introduced | What it changes | Data-race checking |
|---|---|---|---|
| Swift 4 | 2017 | Original migration target from Swift 3 | Still accepted by the 6.x compiler |
| Swift 4.2 | 2018 | Adds synthesized conformances and CaseIterable | Still accepted |
| Swift 5 | 2019 | Default mode for existing code; concurrency available, checking is advisory | Warnings only for data-race issues |
| Swift 6 | 2024 | Full data-race safety enforced by the compiler | Errors 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.
| Property | Since | What it guarantees | Conditions |
|---|---|---|---|
| Source stability | Swift 5 (2019) | Code that compiles today keeps compiling with later compilers in the same language mode | Guaranteed within a language mode; mode changes are opt-in |
| Module stability | Swift 5.1 (2019) | A .swiftinterface file lets a binary framework be imported by future compilers | Requires -enable-library-evolution |
| ABI stability | Swift 5 (2019) | Binaries built with different compiler versions interoperate at runtime | Apple platforms only; the runtime ships in the OS |
| Library evolution | Swift 5.1 (2019) | A library can add members and reorder stored properties without breaking clients | Costs 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.
@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.
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.
| Platform | Architectures | Support level | Minimum deployment version | Notes |
|---|---|---|---|---|
| macOS | arm64, x86_64 | Development and deployment | 13.0 | Full Darwin runtime, Objective-C interop; toolchain in Xcode and standalone |
| iOS / iPadOS | arm64 | Deployment; cross-compiled from macOS | 16.0 | Swift runtime shipped in the OS since iOS 12.2 |
| tvOS | arm64 | Deployment; cross-compiled from macOS | 16.0 | Same runtime model as iOS |
| watchOS | arm64 | Deployment; cross-compiled from macOS | 9.0 | Same runtime model as iOS |
| visionOS | arm64 | Deployment; cross-compiled from macOS | 1.0 | Same runtime model as iOS |
| Ubuntu | arm64, x86_64 | Development and deployment | 22.04 | Corelibs replace Foundation, Dispatch, XCTest; Static Linux SDK available |
| Debian | arm64, x86_64 | Development and deployment | 12 | As Ubuntu |
| Fedora | arm64, x86_64 | Development and deployment | 41 | As Ubuntu |
| Amazon Linux | arm64, x86_64 | Development and deployment | 2023 | As Ubuntu |
| Red Hat UBI | arm64, x86_64 | Development and deployment | 9 | As Ubuntu |
| Windows | arm64, x86_64 | Development and deployment | 10 | MSVC-compatible; no Objective-C runtime; official installer since 5.3 |
| Android | arm64, armv7, x86_64 | Deployment; SDK from the Android workgroup | 9 (API 28) | Official SDK preview since October 2025 |
| WebAssembly | wasm32 | Deployment; SwiftWasm SDK | — | Community-maintained; WASI target, no threads by default |
| FreeBSD | x86_64 | Deployment; community port | — | Community-maintained; tracks the Linux corelibs |
| Linux on IBM Z | s390x | Deployment; community port | — | Community-maintained; server workloads |
| Embedded (bare metal) | ARM Cortex-M, RISC-V | Compilation mode, not a platform port | — | No 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.
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.0 | September 9, 2014 | Yes | No | No |
| 1.1 | October 22, 2014 | Yes | No | No |
| 1.2 | April 8, 2015 | Yes | No | No |
| 2.0 | September 21, 2015 | Yes | No | No |
| 2.1 | October 20, 2015 | Yes | No | No |
| 2.2 | March 21, 2016 | Yes | Yes | No |
| 2.2.1 | May 3, 2016 | Yes | Yes | No |
| 3.0 | September 13, 2016 | Yes | Yes | No |
| 3.0.1 | October 28, 2016 | Yes | Yes | No |
| 3.0.2 | December 13, 2016 | Yes | Yes | No |
| 3.1 | March 27, 2017 | Yes | Yes | No |
| 3.1.1 | April 21, 2017 | Yes | Yes | No |
| 4.0 | September 19, 2017 | Yes | Yes | No |
| 4.0.2 | November 1, 2017 | Yes | Yes | No |
| 4.0.3 | December 5, 2017 | Yes | Yes | No |
| 4.1 | March 29, 2018 | Yes | Yes | No |
| 4.1.1 | May 4, 2018 | No | Yes | No |
| 4.1.2 | May 31, 2018 | Yes | Yes | No |
| 4.1.3 | July 27, 2018 | No | Yes | No |
| 4.2 | September 17, 2018 | Yes | Yes | No |
| 4.2.1 | October 30, 2018 | Yes | Yes | No |
| 4.2.2 | February 4, 2019 | No | Yes | No |
| 4.2.3 | February 28, 2019 | No | Yes | No |
| 4.2.4 | March 29, 2019 | No | Yes | No |
| 5.0 | March 25, 2019 | Yes | Yes | No |
| 5.0.1 | April 18, 2019 | Yes | Yes | No |
| 5.0.2 | July 15, 2019 | No | Yes | No |
| 5.0.3 | August 30, 2019 | No | Yes | No |
| 5.1 | September 10, 2019 | Yes | Yes | No |
| 5.1.1 | October 11, 2019 | No | Yes | No |
| 5.1.2 | November 7, 2019 | Yes | Yes | No |
| 5.1.3 | December 13, 2019 | Yes | Yes | No |
| 5.1.4 | January 31, 2020 | No | Yes | No |
| 5.1.5 | March 9, 2020 | No | Yes | No |
| 5.2 | March 24, 2020 | Yes | Yes | No |
| 5.2.1 | March 30, 2020 | No | Yes | No |
| 5.2.2 | April 15, 2020 | Yes | Yes | No |
| 5.2.3 | April 29, 2020 | No | Yes | No |
| 5.2.4 | May 20, 2020 | Yes | Yes | No |
| 5.2.5 | August 5, 2020 | No | Yes | No |
| 5.3 | September 16, 2020 | Yes | Yes | Yes |
| 5.3.1 | November 13, 2020 | Yes | Yes | Yes |
| 5.3.2 | December 15, 2020 | Yes | Yes | Yes |
| 5.3.3 | January 25, 2021 | No | Yes | Yes |
| 5.4 | April 26, 2021 | Yes | Yes | Yes |
| 5.4.1 | May 25, 2021 | No | Yes | Yes |
| 5.4.2 | June 28, 2021 | Yes | Yes | Yes |
| 5.4.3 | September 9, 2021 | No | Yes | Yes |
| 5.5 | September 20, 2021 | Yes | Yes | Yes |
| 5.5.1 | October 27, 2021 | Yes | Yes | Yes |
| 5.5.2 | December 14, 2021 | Yes | Yes | Yes |
| 5.5.3 | February 9, 2022 | No | Yes | Yes |
| 5.6 | March 14, 2022 | Yes | Yes | Yes |
| 5.6.1 | April 9, 2022 | No | Yes | Yes |
| 5.6.2 | June 15, 2022 | No | Yes | Yes |
| 5.6.3 | September 2, 2022 | No | Yes | Yes |
| 5.7 | September 12, 2022 | Yes | Yes | Yes |
| 5.7.1 | November 1, 2022 | Yes | Yes | Yes |
| 5.8 | March 30, 2023 | Yes | Yes | Yes |
| 5.8.1 | June 1, 2023 | Yes | Yes | Yes |
| 5.9 | September 18, 2023 | Yes | Yes | Yes |
| 5.9.1 | October 19, 2023 | Yes | Yes | Yes |
| 5.9.2 | December 11, 2023 | Yes | Yes | Yes |
| 5.10 | March 5, 2024 | Yes | Yes | Yes |
| 5.10.1 | June 5, 2024 | Yes | Yes | Yes |
| 6.0 | September 17, 2024 | Yes | Yes | Yes |
| 6.0.1 | September 27, 2024 | Yes | Yes | Yes |
| 6.0.2 | October 28, 2024 | Yes | Yes | Yes |
| 6.0.3 | December 13, 2024 | Yes | Yes | Yes |
| 6.1 | April 1, 2025 | Yes | Yes | Yes |
| 6.1.1 | May 24, 2025 | Yes | Yes | Yes |
| 6.1.2 | May 28, 2025 | Yes | Yes | Yes |
| 6.1.3 | September 8, 2025 | Yes | Yes | Yes |
| 6.2 | September 17, 2025 | Yes | Yes | Yes |
| 6.2.1 | November 4, 2025 | Yes | Yes | Yes |
| 6.2.2 | December 9, 2025 | Yes | Yes | Yes |
| 6.2.3 | December 12, 2025 | Yes | Yes | Yes |
| 6.2.4 | February 27, 2026 | Yes | Yes | Yes |
| 6.3 | March 27, 2026 | Yes | Yes | Yes |
| 6.3.1 | April 17, 2026 | Yes | Yes | Yes |
| 6.3.2 | June 4, 2026 | Yes | Yes | Yes |
| 6.3.3 | June 30, 2026 | Yes | Yes | Yes |
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.
| Version | Released | Principal language additions |
|---|---|---|
| 5.1 | September 2019 | Opaque return types (some), property wrappers, module stability |
| 5.4 | April 2021 | Result builders, multiple variadic parameters |
| 5.5 | September 2021 | async/await, structured concurrency, actors, Sendable |
| 5.6 | March 2022 | Existential any spelling introduced |
| 5.7 | September 2022 | Primary associated types, if let shorthand, regex literals, distributed actors |
| 5.9 | September 2023 | Macros, parameter packs, if/switch expressions, borrowing/consuming |
| 5.10 | March 2024 | Full data isolation under strict concurrency checking |
| 6.0 | September 2024 | Swift 6 language mode, typed throws, package access level, noncopyable generics |
| 6.1 | April 2025 | some as lightweight generic syntax, noasync availability |
| 6.2 | September 2025 | Main-actor default isolation, value generics, if case shorthand, implicit conformance suppression |
| 6.3 | March 2026 | Current stable series |
| 6.4 | July 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.
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.
| Modifier | Semantics | Effect at the call site | Typical use |
|---|---|---|---|
inout | Copy-in, copy-out | Caller writes &; the value is written back on return | Mutating a caller's variable in place |
borrowing | Read-only access, no ownership transfer | No retain/release traffic for the call | Large values inspected but not stored |
consuming | Ownership moves into the callee | Caller may not use the value afterwards | Sinks: initialisers, appends, noncopyable handoff |
| (default) | Pass by value, callee borrows | Compiler chooses the cheapest legal strategy | Everything 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.
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.
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.
| Kind | Semantics | Storage | Distinguishing capability | When to reach for it |
|---|---|---|---|---|
struct | Value | Stack or inline in its container | Memberwise init, no inheritance | Default choice for data |
enum | Value | Tag plus payload, size of the largest case | Associated and raw values, exhaustive matching | Closed sets of alternatives |
class | Reference | Heap, reference-counted | Single inheritance, deinit, identity | Shared mutable state, Objective-C interop |
actor | Reference | Heap, reference-counted | Isolated state, implicit Sendable | Concurrency-safe shared state |
protocol | — | — | Requirements, extensions, associated types | Abstraction over unrelated types |
typealias | — | — | Naming only, no new type identity | Readability |
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
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.
| Level | Visible from | Scope | Typical use |
|---|---|---|---|
private | The enclosing declaration and its extensions in the same file | Narrowest | Implementation detail of one type |
fileprivate | The declaring source file | File | Cooperating types in one file |
internal | The declaring module | Module (default) | Everything not deliberately exported |
package | Every module built with the same -package-name | Package (6.0+) | Shared across a multi-module package without becoming public API |
public | Any importing module; not subclassable outside | Public | Library API |
open | Any importing module; subclassable and overridable outside | Widest, classes only | Deliberately 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.
| Form | Behaviour | Failure mode | When to use |
|---|---|---|---|
if let / guard let | Binds the wrapped value in a new scope | None | The default; guard when the rest of the scope needs the value |
?? | Supplies a fallback value | None | A sensible default exists |
?. (chaining) | Propagates nil through a chain of calls | None | Deep access where absence anywhere means absence overall |
map / flatMap | Transforms the wrapped value if present | None | Functional pipelines over an optional |
! (force unwrap) | Traps if nil | Process terminates | An invariant the type system cannot express; document why |
try!, as! | Same, for errors and casts | Process terminates | Same reasoning |
Implicitly unwrapped (Int!) | Unwraps automatically on use | Process terminates | Two-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
}
}
}
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.
| Property | Opaque type | Existential |
|---|---|---|
| Spelling | some P | any P |
| Type identity | One fixed underlying type, hidden from the caller | Varies per value; erased |
| Storage | Same size as the underlying type | Boxed if larger than the inline buffer |
| Dispatch | Static; specialisable | Dynamic, through the witness table |
| Associated types | Usable and constrainable (some Collection<Int>) | Only through primary associated types (5.7+) |
| Heterogeneous collections | Not possible | The reason to use it |
| Self requirements | Allowed | Restricted |
| Default choice | Yes — prefer it | When 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.
| Form | Behaviour | Effect on the caller | When to use |
|---|---|---|---|
try | Propagates the error to the caller | Caller must handle or rethrow | The default |
try? | Converts the result to an optional | Error value is discarded | The error carries no information you will use |
try! | Traps on error | Process terminates | A failure here is a programming error |
throws(E) | Declares the exact error type (6.0+) | Caller can switch exhaustively | Closed error sets; embedded and performance-sensitive code |
rethrows | Throws only if a closure argument throws | Non-throwing callers stay non-throwing | Higher-order functions such as map |
defer | Runs on every exit from the scope | Reverse order of declaration | Cleanup 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>" }
}
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.
| Role | Kind | What it generates | Example use |
|---|---|---|---|
@freestanding(expression) | Freestanding | Produces a value or a compile-time diagnostic | #warning, #externalMacro |
@freestanding(declaration) | Freestanding | Produces one or more declarations | Generating boilerplate types |
@attached(peer) | Attached | Adds declarations beside the one it is attached to | A completion-handler twin of an async method |
@attached(member) | Attached | Adds members to the type or extension | @OptionSet adding init(rawValue:) |
@attached(memberAttribute) | Attached | Adds attributes to the members of a type | Marking every stored property observable |
@attached(accessor) | Attached | Adds accessors, turning a stored property into a computed one | @Observable property tracking |
@attached(extension) | Attached | Adds a conformance, where clause, or members | Synthesising 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.
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.
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)
}
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.
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.
| Domain | Isolation | Typical declaration | Why it is safe |
|---|---|---|---|
| Immutable value | Always isolated | A let of a Sendable type | Nothing can mutate it, so nothing can race |
| Task-local state | Isolated to the current task | A local var inside a function | No other code holds a reference to that memory |
| Actor-isolated state | Isolated to one actor | A stored property of an actor | Access from outside requires await and takes a turn |
| Global-actor-isolated | Isolated to a shared actor | @MainActor var | All access serialised on that actor, across the whole program |
nonisolated | Not isolated | A pure function on an actor | Callable 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 */ }
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.
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 category | What the compiler found | Usual resolution |
|---|---|---|
| Unsafe global or static variable | A mutable static var is reachable from every domain | Make it a let, isolate it to a global actor, or move it into an actor |
| Protocol conformance isolation mismatch | A non-isolated protocol requirement implemented by an isolated type | Isolate the protocol, mark the requirement nonisolated, or use assumeIsolated |
| Non-Sendable type crossing a boundary | A class or closure captured by a task in another domain | Make the type Sendable, pass a sending value, or copy out the data you need |
| Missing annotations in a dependency | A library not yet audited for concurrency | @preconcurrency import, which downgrades its diagnostics to warnings |
| Non-isolated deinitialization | deinit runs in no particular domain | Do not touch isolated state from deinit; capture what you need beforehand |
Unmarked Sendable closure | A callback whose concurrency contract is not in its type | Add @Sendable to the closure type, or enable InferSendableFromCaptures |
Source: Migrating to Swift 6, Common Compiler Errors and Incremental Adoption chapters.
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!) }
}
}
}
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 kind | Increments the count | Type requirement | After deallocation | When to use |
|---|---|---|---|---|
strong (default) | Yes | Non-optional or optional | — | Ownership; the normal case |
weak | No | Optional var only | Becomes nil | Back-references, delegates, parent pointers |
unowned | No | Non-optional | Traps on use after deallocation | Referent provably outlives the reference |
unowned(unsafe) | No | Non-optional | Undefined behaviour on use after deallocation | Performance-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(©)
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.
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.
| Language | Callable from Swift | Callable from that language | How it is enabled | Notes |
|---|---|---|---|---|
| C | Yes, direct | Yes, via @_cdecl | Automatic module map or a bridging header | Pointers map to UnsafePointer family; no ownership inference |
| Objective-C | Yes, near-complete | @objc members only | Bridging header, generated header | Nullability audits drive optionality; Apple platforms only |
| C++ | Yes, since 5.9 | Yes, since 5.9 | -cxx-interoperability-mode=default | C++ structs and classes import as value types; iterators are unsafe in Swift |
| Java | Through swift-java | Through swift-java | JNI-based, community project | Used by the Android workgroup |
| Python | Through PythonKit | — | Dynamic member lookup | Uses @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.
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.
| Facility | Spelling | Active in | What it catches |
|---|---|---|---|
| Assertions | assert, assertionFailure | Debug builds only | Internal invariant you expect to hold |
| Preconditions | precondition, preconditionFailure | Debug and release | A condition callers must satisfy |
| Fatal error | fatalError | All builds, always traps | Unreachable code, unimplemented paths |
| Address sanitizer | -sanitize=address | Opt-in build | Buffer overruns in unsafe code and C interop |
| Thread sanitizer | -sanitize=thread | Opt-in build | Races in code not yet under Swift 6 checking |
| Undefined-behaviour sanitizer | -sanitize=undefined | Opt-in build | C and C++ interop layers |
| Malloc / leaks instrumentation | Instruments, heap, leaks | Runtime tools | Retain 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.
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:
- Pointers are not exposed by default; the
Unsafe-prefixed types are the only way to get one. - Assignment returns no value, so
if i = 0is a compile-time error rather than a silent bug. switchcases do not fall through unlessfallthroughis written.- Variables and constants are always initialized before use, and array bounds are always checked.
- Signed integer overflow traps instead of being undefined behaviour.
- The brace-less single-statement form of
ifandwhileis not accepted. - C-style
for (int i = 0; i < c; i++)loops and the++/--operators were removed in Swift 3.
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.
| Dimension | Swift | Rust | Kotlin | Java |
|---|---|---|---|---|
| Memory management | ARC, deterministic | Ownership and borrowing, compile-time | Tracing GC | Tracing GC |
| Null safety | Optional in the type system | Option in the type system | Nullable types, compiler-checked | Annotations, partly checked |
| Error model | throws with marked call sites; typed throws | Result and ? | Unchecked exceptions | Checked and unchecked exceptions |
| Data-race safety | Compiler-enforced in Swift 6 mode | Compiler-enforced via Send/Sync | Not enforced | Not enforced |
| Concurrency primitives | async/await, actors, task groups | async/await, threads, channels | Coroutines, structured concurrency | Virtual threads, executors |
| Generics | Reified, specialisable, with associated types | Reified, monomorphised, with traits | Reified on the JVM by the compiler | Erased |
| Compilation | AOT to native via SIL and LLVM | AOT to native via MIR and LLVM | Bytecode + JIT, or native | Bytecode + JIT |
| Interop reach | C, Objective-C, C++ | C, and C++ through bindings | Java, Objective-C, JavaScript | C 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.
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.
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/swifton 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.