Skip to content
Structs vs Classes
step 1/7

Reading — step 1 of 7

Learn

~3 min readTypes, Enums, and Errors

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

  • == calls Equatable.== (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 mutating on 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…