SWIFT / CHAPTER 10
Initialization and bounded state
IntermediateEstablish valid values before exposing an instance.
By the end of this chapter
- Use failable initialization for rejection
- Protect mutable state behind methods
- Validate before mutation
The idea
An initializer establishes the stored properties needed for an instance to be valid. A failable initializer uses init? to return nil when its input cannot create an acceptable value. A throwing initializer can instead preserve a specific error. Choose the failure channel from the caller’s needs, then keep the same invariant intact through every later mutating operation.
A bounded model should reject invalid changes before modifying its fields. A read-only public view with controlled mutation prevents arbitrary assignments from bypassing those checks. Arithmetic deserves attention too: checking current + amount against a limit can overflow before the comparison is useful. When current is already within a nonnegative limit, comparing amount with the remaining capacity avoids that premature addition.
struct Stock {private(set) var count:Int;init?(_ count:Int){guard count>=0 else{return nil};self.count=count}
mutating func take(_ n:Int)->Bool {guard n>=0 && n<=count else{return false};count -= n;return true}}
var item=Stock(4)!
print(item.take(5))
print(item.count)false 4
Construction establishes a nonnegative count, and the failed withdrawal preserves that state.
Try it yourself
Design a capacity model whose attempted addition fails without mutation when insufficient room remains.
Use your own editor for this exercise. Runnable challenges on CodeQuest are available for JavaScript, Python, and SQL.
CHECK YOUR UNDERSTANDING