Structs and value semantics
Unit 1 · The Swift value model. This is the foundation everything else stands on — especially Unit 2's concurrency, where value semantics is what makes code safe to send.
Swift's default building block for data is the struct, and structs have value semantics: every time you assign one to a variable or pass it to a function, you get an independent copy. Change the copy and the original is untouched. That single rule removes a whole category of bugs before you write a line of concurrency.
Declaring a struct
struct Temperature { var celsius: Double // A computed property — derived, not stored. var fahrenheit: Double { celsius * 9 / 5 + 32 } } let boiling = Temperature(celsius: 100) print(boiling.fahrenheit) // 212.0
celsius is stored; fahrenheit is computed — it holds no memory of its own and
recalculates on every read. Swift also synthesizes a memberwise initializer
(Temperature(celsius:)) for free, so you rarely write init by hand for a struct.
What "value semantics" actually means
var a = Temperature(celsius: 20) var b = a // b is a COPY b.celsius = 30 print(a.celsius) // 20 — a never changed print(b.celsius) // 30
b = a copies the value. a and b are now two unrelated temperatures. This is the
opposite of how objects behave in most languages, where b = a would make both names
point at the same object.
In Kotlin, val b = a for a regular class copies the reference — a and b point at
the same instance, so mutating one mutates both. Kotlin's data class gives you copy(),
but you have to call it. In Swift the copy is the default and automatic; sharing is the
thing you have to ask for (a class).
The Swift default. When you're modeling data — a user, a message, a settings bundle —
start with a struct. Reach for a class only when you specifically need shared, mutable
identity (one object many things observe) or Apple frameworks require it. "Struct by
default" is the single most important habit in modern Swift.
Why this matters later
Value semantics is not just tidy — it's the property that makes a type safe to hand across
a concurrency boundary. A value nobody else can secretly mutate can't be part of a data
race. When you meet Sendable in Unit 2, remember: structs made of Sendable parts are
Sendable for free, precisely because of the copy rule you just learned.
Your turn
Implement value-preserving arithmetic on a Wallet struct.
Knowledge check
Q: After var y = x; y.balance = 0, what is x.balance if x is a struct?
Unchanged. y is a copy; writing to it can't touch x.
Q: When would you not use a struct?
When you need reference semantics on purpose — a single shared instance many parts of the
app observe and mutate (e.g. a live network connection, or an @Observable view model in
Unit 8). That's a deliberate choice, not the default.