Swift
Swift is a general-purpose, compiled programming language developed by Apple. It is the primary language for iOS, macOS, watchOS, and tvOS development, but also runs on Linux and Windows. Swift is statically typed, memory-safe without a garbage collector (using ARC), and designed to be expressive and fast. It draws inspiration from Rust, Haskell, Ruby, and Python.
Syntax
The simplest Swift program:
print("Hello, world!")Swift does not require a main() function for scripts. For apps and packages, execution starts at the @main entry point or main.swift.
Variables
Variables are declared with var (mutable) and constants with let (immutable). Swift infers types but you can annotate them explicitly.
var name = "Me" // inferred as String
let pi: Double = 3.14159 // explicit type, immutable
var count = 0
count += 1Constants are preferred by default — the compiler will warn you if a var is never mutated.
Data Types
Swift has a rich set of built-in value types. All standard types are structs in Swift, not primitives.
Integers
Int— platform-native (64-bit on modern platforms)Int8,Int16,Int32,Int64— signedUInt,UInt8,UInt16,UInt32,UInt64— unsigned
Floating-point
Double— 64-bit (preferred)Float— 32-bit
Other scalar types
Bool—trueorfalseCharacter— a single Unicode characterString— Unicode string, value type
Collection types
Array<T>/[T]— ordered, resizableDictionary<K, V>/[K: V]— key-value pairsSet<T>— unordered, unique elements
let flags: [String] = ["swift", "linux"]
let scores: [String: Int] = ["alice": 95, "bob": 87]
let unique: Set<Int> = [1, 2, 3, 2, 1] // {1, 2, 3}
// Tuple — lightweight anonymous struct
let point = (x: 3.0, y: 4.0)
print(point.x)Optionals
Optionals represent the absence of a value. A type T? is either a value of type T or nil. This is unlike most languages where any reference can be nil — in Swift, nil is only allowed where explicitly declared.
var email: String? = nil
var username: String? = "me"
// Optional chaining — short-circuits to nil if any step is nil
let upper = username?.uppercased() // String?
// Nil coalescing — provide a default
let display = username ?? "anonymous"
// if let — unwrap safely into a new scope
if let name = username {
print("Logged in as \(name)")
} else {
print("No user")
}
// guard let — early exit, keeps unwrapped value in scope after the guard
func greetUser(_ user: String?) {
guard let name = user else {
print("No user provided")
return
}
print("Hello, \(name)!")
}
// Forced unwrap — crashes at runtime if nil, avoid unless you are certain
let forced = username!In Swift 5.7+ you can write if let username instead of if let username = username (shadowing shorthand).
String interpolation
Strings support interpolation with \(...). Multiline strings use triple quotes.
let age = 30
print("Age: \(age)")
print("Next year: \(age + 1)")
let poem = """
Roses are red,
Violets are blue,
Swift is fast,
And memory-safe too.
"""Conditionals
if and switch are the main branching constructs. Both can be used as expressions.
let score = 72
if score >= 90 {
print("A")
} else if score >= 70 {
print("B")
} else {
print("C")
}
// switch — exhaustive, no fallthrough by default
switch score {
case 90...100:
print("A")
case 70..<90:
print("B")
case let x where x < 0:
print("Invalid: \(x)")
default:
print("C")
}
// switch as an expression (Swift 5.9+)
let grade = switch score {
case 90...100: "A"
case 70..<90: "B"
default: "C"
}guard is used for early returns when a condition is not met, keeping the happy path unindented:
func process(value: Int?) {
guard let v = value, v > 0 else {
print("Invalid")
return
}
print("Processing \(v)")
}Loops
// for-in over ranges and collections
for i in 1...5 {
print(i)
}
for i in 0..<10 where i % 2 == 0 {
print(i) // even numbers
}
for (index, item) in ["a", "b", "c"].enumerated() {
print("\(index): \(item)")
}
// while
var n = 1
while n < 100 {
n *= 2
}
// repeat-while (do-while equivalent)
repeat {
n /= 2
} while n > 1Use continue to skip an iteration, break to exit a loop. Labeled loops allow targeting outer loops:
outer: for i in 0..<5 {
for j in 0..<5 {
if i == j { continue outer }
print("\(i),\(j)")
}
}Functions
Functions are first-class values. Parameters have both an external label (used at call site) and an internal name (used in body). Use _ to suppress the external label.
func greet(name: String, greeting: String = "Hello") -> String {
"\(greeting), \(name)!" // implicit return for single expressions
}
greet(name: "Angelo") // "Hello, Angelo!"
greet(name: "Angelo", greeting: "Ciao") // "Ciao, Angelo!"
// _ suppresses the argument label
func double(_ n: Int) -> Int { n * 2 }
double(5)
// Different external and internal name
func move(from start: Int, to end: Int) { }
move(from: 0, to: 10)
// Variadic parameters
func sum(_ numbers: Int...) -> Int {
numbers.reduce(0, +)
}
sum(1, 2, 3, 4) // 10
// inout — pass by reference (mutate the caller's variable)
func increment(_ value: inout Int) {
value += 1
}
var x = 5
increment(&x) // x is now 6
// Multiple return values via tuple
func minMax(of array: [Int]) -> (min: Int, max: Int) {
(array.min()!, array.max()!)
}
let result = minMax(of: [3, 1, 4, 1, 5])
print(result.min, result.max)Functions that never return are annotated with -> Never:
func crash(_ message: String) -> Never {
fatalError(message)
}Closures
Closures are anonymous functions that capture values from their surrounding scope.
let doubled = [1, 2, 3].map { $0 * 2 } // [2, 4, 6]
let evens = [1, 2, 3, 4].filter { $0 % 2 == 0 }
let total = [1, 2, 3].reduce(0) { $0 + $1 }
// Explicit form
let add: (Int, Int) -> Int = { a, b in a + b }
// Trailing closure syntax
[3, 1, 2].sorted { $0 < $1 }
// Capturing — closures capture variables by reference
func makeCounter() -> () -> Int {
var count = 0
return { count += 1; return count }
}
let counter = makeCounter()
counter() // 1
counter() // 2Enums
Enums in Swift are powerful value types. They can carry associated values, conform to protocols, and have methods.
// Basic enum
enum Direction { case north, south, east, west }
let dir = Direction.north
// Enum with associated values
enum Shape {
case circle(radius: Double)
case rectangle(width: Double, height: Double)
case triangle(base: Double, height: Double)
}
let s = Shape.circle(radius: 5)
switch s {
case .circle(let r):
print("Area: \(Double.pi * r * r)")
case .rectangle(let w, let h):
print("Area: \(w * h)")
case .triangle(let b, let h):
print("Area: \(b * h / 2)")
}
// if let for a single case
if case .circle(let r) = s {
print("Radius: \(r)")
}
// Raw values (must be Equatable & Literal)
enum Planet: Int {
case mercury = 1, venus, earth, mars
}
let earth = Planet(rawValue: 3) // Optional<Planet>
print(Planet.mars.rawValue) // 4
// String raw values
enum HTTPMethod: String {
case get = "GET", post = "POST", delete = "DELETE"
}
// Enum with methods and computed properties
enum Coin: Double {
case penny = 0.01, nickel = 0.05, dime = 0.10, quarter = 0.25
var name: String { rawValue == 0.01 ? "Penny" : String(rawValue) }
}Structs
Structs are value types — they are copied on assignment. They support stored and computed properties, methods, and initializers. Prefer structs over classes when you don’t need inheritance or identity semantics.
struct Point {
var x: Double
var y: Double
// Computed property
var magnitude: Double {
sqrt(x*x + y*y)
}
// Mutating method — required because structs are value types
mutating func translate(dx: Double, dy: Double) {
x += dx
y += dy
}
func distance(to other: Point) -> Double {
sqrt(pow(x - other.x, 2) + pow(y - other.y, 2))
}
}
var p = Point(x: 3, y: 4) // memberwise initializer for free
p.translate(dx: 1, dy: 0)
print(p.magnitude)
// Property observers
struct Temperature {
var celsius: Double {
didSet { print("Changed to \(celsius)°C") }
}
var fahrenheit: Double {
get { celsius * 9/5 + 32 }
set { celsius = (newValue - 32) * 5/9 }
}
}Classes
Classes are reference types — multiple variables can refer to the same instance. They support inheritance and deinitialization. Use classes when you need identity, inheritance, or interop with Objective-C.
class Animal {
var name: String
init(name: String) {
self.name = name
}
func speak() -> String { "..." }
deinit {
print("\(name) deallocated")
}
}
class Dog: Animal {
var breed: String
init(name: String, breed: String) {
self.breed = breed
super.init(name: name)
}
override func speak() -> String { "Woof!" }
}
let dog = Dog(name: "Rex", breed: "Lab")
let same = dog // same reference, not a copy
same.name = "Max"
print(dog.name) // "Max"Struct vs. Class summary:
| Struct | Class | |
|---|---|---|
| Type | Value (copy) | Reference (shared) |
| Inheritance | No | Yes |
deinit | No | Yes |
| ARC overhead | No | Yes |
| Thread safety | Safer (copies) | Requires coordination |
Protocols
Protocols define a blueprint of methods, properties, and requirements. Any type (struct, class, enum) can conform. This is Swift’s primary mechanism for polymorphism.
protocol Drawable {
var color: String { get }
func draw()
}
// Default implementation via extension
extension Drawable {
func drawTwice() {
draw()
draw()
}
}
struct Circle: Drawable {
var color: String
var radius: Double
func draw() {
print("Drawing \(color) circle with radius \(radius)")
}
}
// Protocol as a type
func render(_ shape: any Drawable) {
shape.draw()
}
// Protocol composition
protocol Named { var name: String { get } }
protocol Aged { var age: Int { get } }
func describe(_ entity: any Named & Aged) {
print("\(entity.name), age \(entity.age)")
}Common standard library protocols:
Equatable—==Comparable—<, sortingHashable— use as dictionary key or set elementCodable— encode/decode (JSON, etc.)CustomStringConvertible— customdescriptionIdentifiable— uniqueid(SwiftUI)
Generics
Generics let you write flexible, reusable code that works with any type satisfying given constraints.
// Generic function
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
// Generic type
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { items.popLast() }
var top: Element? { items.last }
var isEmpty: Bool { items.isEmpty }
}
var stack = Stack<Int>()
stack.push(1)
stack.push(2)
// Constrained generics
func largest<T: Comparable>(_ a: T, _ b: T) -> T {
a > b ? a : b
}
// Where clause for complex constraints
func zip<T, U>(_ array1: [T], _ array2: [U]) -> [(T, U)]
where T: Equatable
{
Array(Swift.zip(array1, array2))
}Extensions
Extensions add functionality to existing types — including types you don’t own (retroactive conformance).
extension String {
var isPalindrome: Bool {
self == String(self.reversed())
}
func truncated(to length: Int, suffix: String = "...") -> String {
count > length ? String(prefix(length)) + suffix : self
}
}
"racecar".isPalindrome // true
"Hello, world!".truncated(to: 5) // "Hello..."
// Add protocol conformance via extension
extension Int: CustomStringConvertible {
public var description: String { "Int(\(self))" }
}Error handling
Swift uses a typed error system. Functions that can throw are marked throws. Errors conform to the Error protocol.
enum NetworkError: Error {
case notFound
case unauthorized
case serverError(code: Int)
}
func fetchData(from url: String) throws -> Data {
guard url.hasPrefix("https") else { throw NetworkError.notFound }
// ...
return Data()
}
// do-catch
do {
let data = try fetchData(from: "https://example.com")
print(data)
} catch NetworkError.notFound {
print("Not found")
} catch NetworkError.serverError(let code) {
print("Server error: \(code)")
} catch {
print("Unknown error: \(error)")
}
// try? — converts throws to Optional (nil on error)
let data = try? fetchData(from: "https://example.com")
// try! — crashes on error, avoid unless certain
let data2 = try! fetchData(from: "https://example.com")Swift 6 introduces typed throws:
func parse(_ s: String) throws(ParseError) -> Int { ... }Concurrency
Swift has first-class structured concurrency. async/await avoids callback hell, and actor provides data-race-free mutable state.
// Async function
func fetchUser(id: Int) async throws -> String {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, _) = try await URLSession.shared.data(from: url)
return String(data: data, encoding: .utf8) ?? ""
}
// Launching async work
Task {
do {
let user = try await fetchUser(id: 1)
print(user)
} catch {
print(error)
}
}
// Parallel execution with async let
async let user = fetchUser(id: 1)
async let admin = fetchUser(id: 2)
let both = try await (user, admin) // both fetched concurrently
// Actor — thread-safe reference type
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) { balance += amount }
func withdraw(_ amount: Double) { balance -= amount }
var currentBalance: Double { balance }
}
let account = BankAccount()
await account.deposit(100)
// AsyncSequence — async for-in
for await line in url.lines {
print(line)
}Memory management (ARC)
Swift uses Automatic Reference Counting. You rarely think about memory, but reference cycles can occur with classes.
class Node {
var next: Node? // strong — keeps next alive
weak var prev: Node? // weak — doesn't prevent deallocation, always Optional
unowned var owner: Tree // unowned — like weak but non-optional, crashes if accessed after deallocation
init(owner: Tree) { self.owner = owner }
}Use weak for optional back-references (delegate pattern). Use unowned when the referenced object is guaranteed to outlive the referencing object.
Capture lists in closures:
class ViewModel {
var name = "Me"
func startTimer() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
guard let self else { return }
print(self.name)
}
}
}Pattern matching
switch, if case, for case all support rich pattern matching.
let value: Any = 42
switch value {
case let n as Int where n > 0:
print("Positive int: \(n)")
case let s as String:
print("String: \(s)")
default:
break
}
// for case — filter while iterating
let optionals: [Int?] = [1, nil, 3, nil, 5]
for case let x? in optionals {
print(x) // 1, 3, 5
}
// Destructuring tuples
let pairs = [(1, "one"), (2, "two"), (3, "three")]
for (num, word) in pairs {
print("\(num) = \(word)")
}Type casting
Use is to check type and as? / as! to cast.
let shapes: [Any] = [Circle(color: "red", radius: 5), "not a shape", 42]
for item in shapes {
if let circle = item as? Circle {
circle.draw()
} else if item is String {
print("Skipping string")
}
}Result type
Result<Success, Failure> is useful for passing success/failure values without throwing (e.g. callbacks).
func divide(_ a: Double, by b: Double) -> Result<Double, DivisionError> {
guard b != 0 else { return .failure(.divisionByZero) }
return .success(a / b)
}
switch divide(10, by: 2) {
case .success(let value): print(value)
case .failure(let error): print(error)
}
// get() converts Result back to throwing
let value = try divide(10, by: 0).get()Property wrappers
Property wrappers add reusable behavior to stored properties.
@propertyWrapper
struct Clamped {
private var value: Int
private let range: ClosedRange<Int>
init(wrappedValue: Int, _ range: ClosedRange<Int>) {
self.range = range
self.value = range.clampedValue(wrappedValue) // conceptual
}
var wrappedValue: Int {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Settings {
@Clamped(0...100) var volume: Int = 50
}Common built-in wrappers (in SwiftUI): @State, @Binding, @ObservedObject, @EnvironmentObject, @Published.
Package management (SPM)
Swift Package Manager is the official build and dependency tool.
// Package.swift
let package = Package(
name: "MyLib",
platforms: [.macOS(.v13)],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
.package(url: "https://github.com/vapor/vapor", from: "4.0.0"),
],
targets: [
.executableTarget(
name: "mytool",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.testTarget(name: "myLibTests", dependencies: ["MyLib"]),
]
)swift build # compile
swift run # build and run
swift test # run tests
swift package resolve # fetch dependencies
swift package update # update to latest compatible versionsServer-side Swift
- Vapor — most popular web framework, full-stack with ORM (Fluent), WebSockets, templating (Leaf)
- Hummingbird — lightweight, modular alternative
- Swift OpenAPI Generator — generate type-safe server/client stubs from OpenAPI specs
// Minimal Vapor app
import Vapor
func configure(_ app: Application) throws {
app.get("hello") { req -> String in
"Hello, world!"
}
}Comparison with Rust
Both are modern, memory-safe, and compiled without GC. Key differences:
| Swift | Rust | |
|---|---|---|
| Memory safety | ARC (reference counting) | Borrow checker (compile-time) |
| Null safety | Optionals (T?) | Option<T> |
| Error handling | throws / do-catch | Result<T, E> / ? |
| Concurrency | Actors, async/await | async/await, Send/Sync |
| OOP | Classes + protocols | Structs + traits |
| Ecosystem focus | Apple platforms, server | Systems, WebAssembly, embedded |
| Compile speed | Faster | Slower |