Reading — step 1 of 7
Learn
Apple's Swift Programming Language book treats the struct/class choice as Swift's most consequential design decision. Use structs by default; reach for classes only when you genuinely need reference semantics.
Structs — value types
struct Point {
var x: Double
var y: Double
func distance(to other: Point) -> Double {
let dx = x - other.x
let dy = y - other.y
return (dx*dx + dy*dy).squareRoot()
}
}
var p1 = Point(x: 0, y: 0)
var p2 = p1 // COPY — independent
p2.x = 5
print(p1.x) // 0 — unchanged
print(p2.x) // 5
Structs are value types — assignment, function arguments, and stored properties all COPY. Each variable owns its own data. No shared mutable state.
Swift gets you a memberwise initializer for free:
struct User {
var name: String
var age: Int
}
let u = User(name: "Ada", age: 36) // generated init
When all stored properties have defaults, you also get an empty initializer.
Mutating methods
Methods that change the struct's state must be marked mutating:
struct Counter {
var count = 0
mutating func increment() {
count += 1
}
}
var c = Counter()
c.increment() // OK — c is var
let d = Counter()
// d.increment() // ERROR — d is let; mutating not allowed
let for structs locks not just the binding but also any nested mutation. Powerful immutability.
Classes — reference types
class Account {
var balance: Double
init(balance: Double) {
self.balance = balance
}
func deposit(_ amount: Double) {
balance += amount
}
}
let a = Account(balance: 100)
let b = a // SAME instance — both refer to the same object
b.deposit(50)
print(a.balance) // 150 — both see the change
Classes must define their own init (no free memberwise). Class instances pass by REFERENCE — assignment shares the object.
A let binding to a class blocks reassignment of the BINDING, but the OBJECT remains mutable:
let a = Account(balance: 100)
a.balance = 999 // OK — modifying the object
// a = Account(...) // ERROR — can't rebind let
Identity vs equality
==callsEquatable.==(value equality, you implement)===checks identity (same OBJECT for classes; doesn't compile for structs)
let a = Account(balance: 100)
let b = a
let c = Account(balance: 100)
a === b // true — same instance
a === c // false — different instances, even though same balance
When to use which
Use a struct when:
- The type represents a value (Point, Date, currency, configuration)
- Equality is by content, not identity
- Changes should produce new copies (no shared mutable state surprises)
- The data is small enough that copying is cheap (or COW backed — Swift's String, Array, Dictionary are all structs)
Use a class when:
- You need reference semantics (multiple variables sharing one object)
- The type has identity that outlives any field — e.g., a network connection, a window, a database session
- You need inheritance (only classes inherit)
- You need deinitializers for cleanup
- You're bridging to Objective-C / Cocoa
Inheritance — classes only
class Animal {
var name: String
init(name: String) { self.name = name }
func speak() { print("\(name) says something") }
}
class Dog: Animal {
override func speak() { print("\(name) barks") }
}
Dog(name: "Rex").speak() // Rex barks
Overrides require the override keyword. Mark with final to prevent subclassing.
Structs cannot inherit from other structs (use composition or protocol-oriented programming instead).
Common mistakes
- Reaching for classes by habit — Swift biases toward structs. Most types should be structs.
- Forgetting
mutatingon methods that change state — compile error. - Confusing struct semantics across function boundaries — modifying a passed struct doesn't change the caller's copy.
- Using a struct when you need shared mutable state — every dot-mutate creates a new value; sharing it is awkward without classes or wrappers.
- Inheritance inside structs — not allowed. Use protocol composition.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…