Swift
14 min read

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:

swift
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.

swift
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

• Bool — true 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

swift
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.

swift
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.

swift
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.

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

swift
func process(value: Int?) {
guard let v = value, v > 0 else {
print("Invalid")
return
}
print("Processing \(v)")
}

Loops

swift
// 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:

swift
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.

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

swift
func crash(_ message: String) -> Never {
fatalError(message)
}

Closures

Closures are anonymous functions that capture values from their surrounding scope.

swift
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.

swift
// 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.

swift
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.

swift
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.

swift
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.

swift
// 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).

swift
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.

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

swift
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.

swift
// 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.

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

swift
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.

swift
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.

swift
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).

swift
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.

swift
@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.

swift
// 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"]),
]
)

sh
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

swift
// 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