Structs and value semantics
The type you should reach for by default, and the copying behaviour that removes an entire category of bug.
By the end you will be able to
- Define structs with stored properties, computed properties and methods
- Explain what `mutating` means and why structs need it
- Predict the result of assigning or passing a struct
Point is a struct. What does this print?
struct Point { var x: Int; var y: Int }
var a = Point(x: 1, y: 2)
var b = a
b.x = 99
print(a.x, b.x)Defining a struct
struct Temperature {
var celsius: Double
var fahrenheit: Double {
celsius * 9 / 5 + 32
}
func isFreezing() -> Bool {
celsius <= 0
}
mutating func warm(by degrees: Double) {
celsius += degrees
}
}
Four things are on display:
- Stored property —
celsiusholds a value. - Computed property —
fahrenheitcalculates on each access; it stores nothing. - Method — an ordinary function that can see
self. - Mutating method — one that changes the struct, which needs saying out loud.
The memberwise initializer, for free
You never wrote init above, yet Temperature(celsius: 18) worked. Swift synthesises a memberwise initializer for every struct, taking each stored property in declaration order. var properties with default values become optional arguments (a let with a default is settled — it is left out of the initializer entirely):
struct Settings {
var theme: String = "dark"
var fontSize: Int = 14
}
Settings() // both defaults
Settings(fontSize: 18) // theme defaults
Settings(theme: "light", fontSize: 12)
Write your own init when you need validation or a more convenient signature.
mutating — why it exists
A method on a struct cannot change the struct unless it is marked mutating. This looks like ceremony until you see what it enables:
let fixed = Temperature(celsius: 20)
// fixed.warm(by: 5) ← does not compile
Because fixed is a let, Swift refuses to call a mutating method on it. The compiler is telling you that a constant is genuinely constant — not "the reference is constant but the contents can change underneath you". mutating is what makes that check possible.
Under the hood, a mutating method receives self as an inout parameter. That is also why a mutating method can replace the whole value:
mutating func reset() {
self = Temperature(celsius: 0)
}Property observers
willSet and didSet run around a change to a stored property:
newValue and oldValue are provided automatically. Observers do not fire during initialization — only on later changes. Picture the refused world: a didSet firing mid-init would run your reaction code against a half-built value — reading a sibling property that has not been assigned yet — and every freshly constructed value would announce a phantom "change" nobody made. Observers watch changes to a whole value; during init there is no whole value yet to change.
Structs are copied when passed to functions. What does this print?
struct Counter {
var count = 0
mutating func increment() {
count += 1
}
}
func bumpTwice(_ counter: Counter) -> Counter {
var copy = counter
copy.increment()
copy.increment()
return copy
}
var original = Counter()
original.increment()
let result = bumpTwice(original)
print(original.count)
print(result.count)Static members
static attaches something to the type rather than an instance:
struct Temperature {
static let absoluteZero = Temperature(celsius: -273.15)
static func fromFahrenheit(_ f: Double) -> Temperature {
Temperature(celsius: (f - 32) * 5 / 9)
}
}
Temperature.absoluteZero
Temperature.fromFahrenheit(98.6)
Static properties are computed lazily on first use and only once, which makes them the standard way to express constants and shared configuration.
Why does Swift require the mutating keyword on struct methods that change properties?
Model a shopping basket
Build a Basket struct with an items array of (name, price) tuples, a computed total, and a mutating add method.
Validate in an initializer
Give Percentage a custom initializer that clamps any input into 0…100.
Observe a change
Add a didSet observer to progress that prints done exactly when it reaches 100 — and not on any other change.
Sketch a struct for a calendar event: a title, a start time, a duration, and an optional location. Decide which properties are let and which are var, and write one sentence for each explaining why.
You can now:
- Define structs with stored properties, computed properties, methods and observers
- Explain
mutatingand predict when the compiler will reject a call - Predict what assigning or passing a struct does
Next up: classes — reference semantics, inheritance, and how to decide which of the two you actually need.