Guarding State
Part of the Object Oriented Programming section of Coddy's Swift journey. Lesson 19 of 57.
An invariant is a rule that must always hold for a value, such as "a balance is never negative". A type protects its invariants by checking every change before applying it:
struct BankAccount {
private(set) var balance = 0
mutating func withdraw(_ amount: Int) -> Bool {
guard amount > 0, amount <= balance else { return false }
balance -= amount
return true
}
mutating func deposit(_ amount: Int) {
guard amount > 0 else { return }
balance += amount
}
}
var acc = BankAccount()
acc.deposit(100)
print(acc.withdraw(150))
print(acc.withdraw(40))
print(acc.balance)Output:
false
true
60When a caller needs to know why a change was refused, the method can throw an error instead of returning false. The error type is an enum that conforms to Error:
enum TransferError: Error {
case invalidAmount
case insufficientFunds(missing: Int)
}
struct Account {
private(set) var balance: Int
mutating func withdraw(_ amount: Int) throws {
guard amount > 0 else { throw TransferError.invalidAmount }
guard amount <= balance else { throw TransferError.insufficientFunds(missing: amount - balance) }
balance -= amount
}
}
var acc = Account(balance: 30)
do {
try acc.withdraw(50)
} catch TransferError.insufficientFunds(let missing) {
print("missing \(missing)")
} catch {
print("other error")
}Output:
missing 20Keep every change of the protected data inside methods that check it. If the property were settable from outside, any line of code could break the rule without the type noticing:
struct Temperature {
private(set) var kelvin: Double // must stay >= 0
mutating func cool(by amount: Double) {
kelvin = max(0, kelvin - amount) // the only way to lower it
}
}An operation that changes several properties must leave them consistent even when it fails halfway. Check everything first, then change the state:
struct Inventory {
private(set) var stock: [String: Int] = ["tea": 2, "jam": 1]
mutating func sell(_ items: [String]) -> Bool {
var needed: [String: Int] = [:]
for i in items { needed[i, default: 0] += 1 }
for (item, n) in needed where (stock[item] ?? 0) < n { return false }
for (item, n) in needed { stock[item]! -= n }
return true
}
}
var inv = Inventory()
print(inv.sell(["tea", "jam", "jam"]))
print(inv.stock["tea"]!)
print(inv.sell(["tea", "jam"]))
print(inv.stock["tea"]!)Output:
false
2
true
1Challenge
EasyComplete the struct Seats, which sells tickets for a show with capacity seats. sold is private(set). buy(_:) throws TicketError.invalidCount for a count below 1, TicketError.soldOut when no seat is left, and TicketError.notEnough(left:) when fewer seats are left than requested; otherwise it adds the tickets to sold.
The supplied code reads the capacity and one requested count per line. For each it prints sold 3, left 7, invalid count, sold out or only 2 left.
Your code goes in TicketError.swift and Seats.swift. main.swift holds the supplied input/output code and cannot be edited.
Try it yourself
// Supplied input/output code: keep it as it is
var input: [String] = []
while let line = readLine() { input.append(line) }
var seats = Seats(capacity: Int(input[0])!)
for line in input.dropFirst() {
let n = Int(line)!
do {
try seats.buy(n)
print("sold \(n), left \(seats.capacity - seats.sold)")
} catch TicketError.invalidCount {
print("invalid count")
} catch TicketError.soldOut {
print("sold out")
} catch TicketError.notEnough(let left) {
print("only \(left) left")
} catch {
print("unexpected")
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesMethods and selfInitializersClasses Are ReferencesRecap - Library Card4Encapsulation
Access ControlRead-Only From OutsideGuarding StateFailable InitializersRecap - Bank Account7Polymorphism and Generics
PolymorphismType CastingGeneric FunctionsGeneric TypesRecap - Shape Calculator5Inheritance
SubclassesOverriding MethodsInitializers and superfinal and HierarchiesRecap - Employee Hierarchy11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Swift playground