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 += 1

Constants 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 — signed
  • UInt, UInt8, UInt16, UInt32, UInt64 — unsigned

Floating-point

  • Double — 64-bit (preferred)
  • Float — 32-bit

Other scalar types

  • Booltrue or false
  • Character — a single Unicode character
  • String — Unicode string, value type

Collection types

  • Array<T> / [T] — ordered, resizable
  • Dictionary<K, V> / [K: V] — key-value pairs
  • Set<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 > 1

Use 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()  // 2

Enums

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:

StructClass
TypeValue (copy)Reference (shared)
InheritanceNoYes
deinitNoYes
ARC overheadNoYes
Thread safetySafer (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<, sorting
  • Hashable — use as dictionary key or set element
  • Codable — encode/decode (JSON, etc.)
  • CustomStringConvertible — custom description
  • Identifiable — unique id (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 versions

Server-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:

SwiftRust
Memory safetyARC (reference counting)Borrow checker (compile-time)
Null safetyOptionals (T?)Option<T>
Error handlingthrows / do-catchResult<T, E> / ?
ConcurrencyActors, async/awaitasync/await, Send/Sync
OOPClasses + protocolsStructs + traits
Ecosystem focusApple platforms, serverSystems, WebAssembly, embedded
Compile speedFasterSlower