A way to thread dependencies through your app using Swift's protocol system.
You have services that need other services:
struct OrderService {
func create(userId: String, items: [Item]) -> Order {
// Need to look up user... somehow
// Need to check inventory... somehow
// Need to charge payment... somehow
}
}Option 1: Singletons. Easy to write, impossible to test.
let user = UserService.shared.find(userId) // Good luck mocking thisOption 2: Pass everything explicitly. Safe, but tedious.
struct OrderService {
let userService: UserService
let inventoryService: InventoryService
let paymentService: PaymentService
let db: Database
init(userService: UserService, inventoryService: InventoryService,
paymentService: PaymentService, db: Database) {
// ...
}
}
// Somewhere at the root of your app:
let db = PostgreSQL()
let userService = UserService(db: db)
let inventoryService = InventoryService(db: db)
let paymentService = PaymentService(stripe: stripe, db: db)
let orderService = OrderService(
userService: userService,
inventoryService: inventoryService,
paymentService: paymentService,
db: db
)
// ... repeat for every serviceEvery new service means more wiring. Every new dependency means updating constructors throughout the chain.
What we want: Pass one thing, get access to everything you need, but only what you declare.
Here's the fundamental move:
// 1. Declare "I need a database"
protocol HasDatabase {
var db: Database { get }
}
// 2. Write a service that works with ANY type that has a database
struct UserService<Deps: HasDatabase> {
let deps: Deps
func find(id: String) -> User? {
deps.db.query("SELECT * FROM users WHERE id = ?", id)
}
}
// 3. Create a bundle with a database
struct App: HasDatabase {
let db = PostgreSQL()
}
// 4. It just works
let app = App()
let userService = UserService(deps: app)
userService.find(id: "123")UserService doesn't know what Deps is. It just knows it has a db property. You could pass it App, or TestApp, or anything with a database.
This is the Reader pattern: instead of passing Database to every function, you pass an "environment" once and pull what you need from it.
Now let's add OrderService that needs UserService:
protocol HasUser {
var user: UserService<Self> { get }
}
struct OrderService<Deps: HasDatabase & HasUser> {
let deps: Deps
func create(userId: String, items: [Item]) -> Order {
guard let user = deps.user.find(id: userId) else {
throw OrderError.userNotFound
}
// ...
}
}But wait—how does App get a user property? We could add it manually:
struct App: HasDatabase, HasUser {
let db = PostgreSQL()
var user: UserService<App> { UserService(deps: self) }
}This works. But now every bundle needs to manually wire every service. We're back to boilerplate.
Swift lets us provide default implementations via protocol extensions:
protocol HasUser: HasDatabase {
var user: UserService<Self> { get }
}
extension HasUser {
var user: UserService<Self> { UserService(deps: self) }
}Now ANY type that conforms to HasUser automatically gets a user property, as long as it also has HasDatabase (which HasUser inherits).
struct App: HasUser { // Just declare the conformance
let db = PostgreSQL()
}
App().user.find(id: "123") // user property comes from the extensionThe wiring happens automatically through the protocol extension. self in the extension is App, so UserService captures App as its deps.
We've welded HasUser to a specific implementation. What if we want MongoUserService in production and MockUserService in tests?
// These can't both exist:
extension HasUser {
var user: UserService<Self> { UserService(deps: self) } // Postgres
}
extension HasUser {
var user: MockUserService { MockUserService() } // Mock
}We need to separate "I have user capabilities" from "use this specific implementation."
Split it into two protocols:
// Abstract: "I have user capabilities"
protocol HasUser {
associatedtype UserImpl: UserServiceProtocol
var user: UserImpl { get }
}
// Concrete: "Use PostgresUserService for HasUser"
protocol LinkPostgresUser: HasUser, HasDatabase { }
extension LinkPostgresUser {
var user: PostgresUserService<Self> { PostgresUserService(deps: self) }
}
// Alternative: "Use MockUserService for HasUser"
protocol LinkMockUser: HasUser { }
extension LinkMockUser {
var user: MockUserService { MockUserService() }
}Now bundles choose their implementation by which Link they conform to:
struct App: LinkPostgresUser {
let db = PostgreSQL()
}
struct TestApp: LinkMockUser {
// No database needed—mock doesn't use one
}This is the "linking" step. HasUser is like a header file declaring a symbol exists. LinkPostgresUser is like -lpostgres telling the linker which library provides it.
Three mechanisms working together:
1. Structural typing via protocol composition
Deps: HasDatabase & HasCacheThis means "any type with db and cache properties." No specific type required. The shape is the contract.
2. Environment capture
struct UserService<Deps: HasDatabase> {
let deps: Deps // Captured once at construction
func find(id: String) -> User? {
deps.db.query(...) // Accessed implicitly thereafter
}
}Services are functions from Deps → Capabilities. The struct just gives you named access to the captured environment.
3. Late binding via conformance
extension LinkPostgresUser {
var user: ... { PostgresUserService(deps: self) }
}The extension doesn't know what self is—the conforming type decides. Implementation is chosen when you write struct App: LinkPostgresUser, not at the call site.
Here's something subtle. If two services both need a database:
struct App: LinkUser, LinkOrder {
let db = PostgreSQL()
}Both LinkUser and LinkOrder extensions receive self = App. When UserService accesses deps.db and OrderService accesses deps.db, they get the same instance.
No configuration. Same property name = same instance. The sharing is structural.
Each service only sees what it declares:
struct UserService<Deps: HasDatabase & HasCache> {
let deps: Deps
// Can access: deps.db, deps.cache
// Cannot access: deps.payment (even if App has it)
}
struct OrderService<Deps: HasUser & HasInventory> {
let deps: Deps
// Can access: deps.user, deps.inventory
// Cannot access: deps.db directly (that's User's concern)
}OrderService uses UserService but doesn't know UserService uses a database. Transitive dependencies are hidden. If UserService later moves to a microservice, OrderService doesn't change.
Four concepts:
| Concept | Purpose | Example |
|---|---|---|
| Service | The capability interface | protocol UserService { func find(id:) -> User? } |
| Needs | What's required | HasDatabase, HasCache (compose them) |
| Impl | How it works | struct PostgresUser<D: HasDatabase & HasCache> |
| Link | Which impl to use | protocol LinkPostgresUser + extension |
A complete service:
// Service: what it does
protocol UserService {
func find(id: String) -> User?
func create(email: String) async throws -> User
}
// Needs: what's required (atomic, composable)
protocol HasDatabase {
associatedtype DB: Database
var db: DB { get }
}
// Impl: how it works
struct PostgresUser<Deps: HasDatabase>: UserService {
let deps: Deps
func find(id: String) -> User? {
deps.db.query("SELECT * FROM users WHERE id = ?", id).first
}
func create(email: String) async throws -> User {
let user = User(id: UUID().uuidString, email: email)
try await deps.db.insert("users", user)
return user
}
}
// Link: selects this implementation
protocol HasUser {
associatedtype U: UserService
var user: U { get }
}
protocol LinkPostgresUser: HasUser, HasDatabase { }
extension LinkPostgresUser {
var user: some UserService { PostgresUser(deps: self) }
}// Production
struct App: LinkPostgresUser, LinkStandardOrder, LinkStripePayment {
let db = PostgreSQL()
let stripe = StripeAPI(key: productionKey)
}
// Test: real database, mock payment
struct TestApp: LinkPostgresUser, LinkStandardOrder, LinkMockPayment {
let db = PostgreSQL.testContainer()
}
// View only knows about capabilities, not implementations
struct CheckoutView<Services: HasUser & HasOrder>: View {
let services: Services
var body: some View {
Button("Checkout") {
let user = services.user.current()
let order = services.order.create(userId: user.id, items: cart)
}
}
}You could thread dependencies as function parameters:
func findUser(db: Database, id: String) -> User?
func createOrder(findUser: (String) -> User?, db: Database, items: [Item]) -> OrderProblems:
createOrderneedsfindUserANDdb. ButfindUseralready usesdbinternally. You're passingdbtwice.- 10 services × 5 methods = 50 functions to wire manually.
- To mock
findUser, you rewire everything that captured it.
The bundle solves this. deps.db in UserService and deps.db in OrderService are the same because deps is the same object. Sharing is automatic. Mocking is surgical—swap one Link, everything else stays real.
Runtime DI containers work fine. They're easier to learn but:
- Errors at runtime, not compile time
- Can't see what depends on what in the code
- Overhead from runtime lookups
The Service Linker gives you compile-time checking. If a dependency is missing, it won't build. The wiring is visible in the type signatures.
Tradeoff: you learn the protocol mechanics upfront, but then the compiler catches mistakes forever.
Don't bother if:
- You have < 10 services (just wire manually)
- Team isn't comfortable with Swift generics/protocols
- You want minimal concepts to learn
Consider it if:
- Service graph is complex and growing
- You want surgical test mocking (swap one thing, keep rest real)
- You care about compile-time safety
- You're okay with upfront learning cost
Because everything is concrete at compile time:
// You write
app.user.find(id: "123")
// Compiler sees
PostgresUser<App>(deps: app).find(id: "123")
// After inlining
app.db.query("SELECT * FROM users WHERE id = ?", "123").firstThe abstraction exists in source code. The binary is the same as if you wrote it by hand.
The Service Linker Pattern is:
- Reader pattern: capture environment once, access implicitly
- Structural typing: protocol composition describes the shape
- Late binding: conformance chooses implementation
You get automatic dependency threading, automatic sharing, scoped visibility, and compile-time safety. You pay with protocol boilerplate and conceptual overhead.
It's not magic—it's just protocols and generics, composed carefully.
// ═══════════════════════════════════════════════════════════════════════════ // Kiosk.swift — the Service Linker pattern, end to end, in one literate file // ═══════════════════════════════════════════════════════════════════════════ // // A coffee-shop point-of-sale: a menu, open tabs, ingredient stock, a payment // gateway, a clock, and a write-ahead journal. Small enough to read in one // sitting, complete enough to exercise every part of the pattern: // // Part 1 State slices — plain copyable data, one per domain // Part 2 Capabilities — borrow = read right, mutate = write right // Part 3 Reads — logic as constrained extensions // Part 4 Commands — writes with compiler-checked footprints // Part 5 The substitution tier — gateways, clocks, and their Links // Part 6 Resources — unique I/O handles behind capabilities // Part 7 The World — the composition root (the ONLY file tier // allowed to name it) // Part 8 Snapshots — undo as an assignment // Part 9 The shell — one actor owns everything // Part 10 Tests — surgical substitution, literal setup // // SYNTAX FRONTIER — this file uses Swift 6.3/6.4 features whose spelling may // still shift before stabilization. Marked `// [6.4]` where they appear: // • `borrow { } mutate { }` accessors, and accessor requirements in // protocol declarations (the most speculative spelling here) // • ~Copyable associated types and generic parameters // • MutableRef, UniqueBox // Everything else is current Swift. // // ═══════════════════════════════════════════════════════════════════════════ // Part 1 — State slices // ═══════════════════════════════════════════════════════════════════════════ // // Each domain's state is a plain copyable struct. Copyability is the point: // it is what makes snapshots an assignment (Part 8). Slices use value-type // collections so copy-on-write prices a snapshot at O(changed slices). // // Note that identity generation lives *inside* a slice (`TabsState.nextID`) // rather than coming from UUID(): state transitions stay deterministic, so a // command log replays bit-for-bit. The same motive puts the clock behind a // Link in Part 5. struct Item: Hashable, Codable { struct ID: Hashable, Codable { let raw: Int } var name: String var price: Decimal // in the till's currency var recipe: [Ingredient: Int] // ingredient → units consumed per serving } enum Ingredient: String, Hashable, Codable { case beans, milk, oatMilk, syrup, cup } struct MenuState: Codable { var items: [Item.ID: Item] = [:] } struct Tab: Codable { struct ID: Hashable, Codable { let raw: Int } var customer: String var lines: [Item.ID] = [] var settled: Bool = false } struct TabsState: Codable { var open: [Tab.ID: Tab] = [:] var nextID: Int = 1 mutating func issueID() -> Tab.ID { defer { nextID += 1 } return Tab.ID(raw: nextID) } } struct StockState: Codable { var units: [Ingredient: Int] = [:] enum Shortage: Error { case insufficient(Ingredient) } mutating func consume(_ ingredient: Ingredient, count: Int) throws { let have = units[ingredient, default: 0] guard have >= count else { throw Shortage.insufficient(ingredient) } units[ingredient] = have - count } } struct Receipt: Codable { var tab: Tab.ID var total: Decimal var chargedAt: Date var reference: String // gateway's charge reference } // ═══════════════════════════════════════════════════════════════════════════ // Part 2 — Capabilities: access rights as protocols // ═══════════════════════════════════════════════════════════════════════════ // // A capability is one property, and its accessor list IS its meaning. // `borrow` is a read right: the witness must project storage in place — it // cannot legally hand back a stale copy. `mutate` is a write right, granted // by a *refining* protocol, so read-only code is read-only by type, not by // promise. // // No associated types here: a state slice has one true type, and there is // nothing to substitute. Associated types are reserved for Part 5, where // substitution is real. // // These protocols are ~Copyable because the environment that witnesses them // (the World, Part 7) is noncopyable — generic code must be able to range // over it. protocol HasMenu: ~Copyable { var menu: MenuState { borrow } // [6.4] } protocol HasMutableMenu: HasMenu, ~Copyable { var menu: MenuState { borrow mutate } // [6.4] } protocol HasTabs: ~Copyable { var tabs: TabsState { borrow } // [6.4] } protocol HasMutableTabs: HasTabs, ~Copyable { var tabs: TabsState { borrow mutate } // [6.4] } protocol HasStock: ~Copyable { var stock: StockState { borrow } // [6.4] } protocol HasMutableStock: HasStock, ~Copyable { var stock: StockState { borrow mutate } // [6.4] } // ═══════════════════════════════════════════════════════════════════════════ // Part 3 — Reads: logic as constrained extensions // ═══════════════════════════════════════════════════════════════════════════ // // There are no service objects. `self` is the environment; the `where` // clause is the function's declared footprint. Everything below is // parametric — none of it can name the World, so none of it can touch a // slice it didn't declare. That parametricity is the access control. // // Reads borrow, so any number of them may overlap and compose. extension HasMenu where Self: ~Copyable { /// Sum of menu prices for a list of items. Reads: menu. func price(of lines: [Item.ID]) -> Decimal { lines.reduce(Decimal.zero) { total, id in total + (menu.items[id]?.price ?? 0) } } } extension HasMenu where Self: HasStock & ~Copyable { /// Can the kitchen actually make this item right now? Reads: menu, stock. func isAvailable(_ id: Item.ID) -> Bool { guard let item = menu.items[id] else { return false } return item.recipe.allSatisfy { ingredient, needed in stock.units[ingredient, default: 0] >= needed } } } extension HasTabs where Self: HasMenu & ~Copyable { /// A printable summary of one tab. Reads: tabs, menu. func summary(of id: Tab.ID) -> String { guard let tab = tabs.open[id] else { return "no such tab" } let names = tab.lines.compactMap { menu.items[$0]?.name } return "\(tab.customer): \(names.joined(separator: ", ")) — \(price(of: tab.lines))" } } // A generic "feature" — what a view layer looks like under the parametric // perimeter. It borrows the environment for the duration of the call and is // provably unable to mutate anything: its constraints contain no `Mutable`. func renderBoard<E: HasTabs & HasMenu & ~Copyable>(_ env: borrowing E) -> String { env.tabs.open.keys .sorted { $0.raw < $1.raw } .map { env.summary(of: $0) } .joined(separator: "\n") } // ═══════════════════════════════════════════════════════════════════════════ // Part 4 — Commands: writes with typed footprints // ═══════════════════════════════════════════════════════════════════════════ // // A command is a `mutating` function constrained on *mutable* capabilities. // Read its constraint list as a compiler-checked effect declaration. The set // of writers of any slice is exactly the set of functions constrained on its // `HasMutable…` — a findable, finite set. enum KioskError: Error { case unknownTab, unknownItem, alreadySettled } extension HasMutableTabs where Self: ~Copyable { /// Writes: tabs. Reads: nothing else. mutating func openTab(for customer: String) -> Tab.ID { let id = tabs.issueID() tabs.open[id] = Tab(customer: customer) return id } } extension HasMutableTabs where Self: HasMenu & HasStock & ~Copyable { /// Writes: tabs. Reads: menu, stock (to refuse unmakeable orders early). mutating func add(_ item: Item.ID, to tab: Tab.ID) throws { guard tabs.open[tab] != nil else { throw KioskError.unknownTab } guard isAvailable(item) else { throw KioskError.unknownItem } tabs.open[tab]!.lines.append(item) } } // The big one. Note the internal structure every nontrivial command shares: // a read phase (borrows, freely composed across capabilities), then a write // phase that touches one slice at a time. `MutableRef` hoists the stock // access once and holds it across the loop — without it, every iteration // re-runs the accessor chain. It is non-escapable: the compiler proves the // hoisted access cannot outlive this function body. extension HasMutableTabs where Self: HasMutableStock & HasMenu & HasPayment & HasClock & HasJournal & ~Copyable { /// Writes: tabs, stock, payment session, journal. Reads: menu, clock. /// All of that is in the constraint list above — nothing is ambient. mutating func settle(_ id: Tab.ID) throws -> Receipt { // ── read phase ────────────────────────────────────────────── guard let tab = tabs.open[id] else { throw KioskError.unknownTab } guard !tab.settled else { throw KioskError.alreadySettled } let total = price(of: tab.lines) let recipeRuns = tab.lines.compactMap { menu.items[$0]?.recipe } // ── write phase: stock, hoisted ───────────────────────────── var stockRef = MutableRef(&stock) // [6.4] for recipe in recipeRuns { for (ingredient, count) in recipe { try stockRef.value.consume(ingredient, count: count) } } // ── write phase: payment (a stateful gateway — see Part 5) ── let reference = try payment.charge(total) // ── write phase: tabs, then the journal resource ──────────── tabs.open[id]!.settled = true let receipt = Receipt(tab: id, total: total, chargedAt: clock.now(), reference: reference) journal.append(.settled(receipt)) return receipt } } // ═══════════════════════════════════════════════════════════════════════════ // Part 5 — The substitution tier: Links // ═══════════════════════════════════════════════════════════════════════════ // // Tabs, stock, and menu have one implementation each — they got no Link and // no associated type. The payment gateway and the clock genuinely vary // (production / test), so here, and only here, the capability gains an // associated type and Link protocols select the witness. protocol PaymentGateway: ~Copyable { mutating func charge(_ amount: Decimal) throws -> String } protocol HasPayment: ~Copyable { associatedtype Gateway: PaymentGateway & ~Copyable // [6.4] var payment: Gateway { borrow mutate } // [6.4] } // The production gateway holds session state — nonces, an auth token — so // `charge` is honestly `mutating`, and the Link must demand a stored slice // from the World and *project* it. (The old pattern hid this mutation inside // a class; the access typing surfaces it.) struct StripeSession: Codable { var authToken: String = "" var nonce: Int = 0 } struct StripeGateway: PaymentGateway { var session: StripeSession mutating func charge(_ amount: Decimal) throws -> String { session.nonce += 1 // ... synchronous wire call elided; async transport would live // outside the World and re-enter as a command (see Part 9) ... return "ch_\(session.nonce)" } } protocol LinkStripePayment: HasPayment, ~Copyable { var stripeSession: StripeSession { get mutating set } // demanded storage } extension LinkStripePayment { var payment: StripeGateway { borrow { StripeGateway(session: stripeSession) } // [6.4] — see note mutate { // [6.4] // Projection of a wrapper over a stored slice. Whether a mutate // accessor may yield a synthesized wrapper (with writeback) or // must project storage verbatim is the sharpest open spelling // question in this file; the fallback is to store StripeGateway // itself as the slice. The design is unaffected. &storedGatewayView } } } // The mock pays attention to nothing and needs no storage — for trivially // copyable implementations, construct-on-access is harmless and the Link // demands nothing. struct MockGateway: PaymentGateway { mutating func charge(_ amount: Decimal) throws -> String { "mock_ok" } } protocol LinkMockPayment: HasPayment, ~Copyable { } extension LinkMockPayment { var payment: MockGateway { borrow { MockGateway() } // [6.4] mutate { &scratchMock } // [6.4] same caveat } } // The clock: never call Date() in a command — replay dies. Production links // the system clock; tests link a fixed one. protocol KioskClock { func now() -> Date } protocol HasClock: ~Copyable { associatedtype C: KioskClock var clock: C { borrow } // [6.4] } struct SystemClock: KioskClock { func now() -> Date { Date() } } protocol LinkSystemClock: HasClock, ~Copyable { } extension LinkSystemClock { var clock: SystemClock { SystemClock() } } struct FixedClock: KioskClock { var frozen: Date func now() -> Date { frozen } } protocol LinkFixedClock: HasClock, ~Copyable { var frozenNow: Date { get } } extension LinkFixedClock { var clock: FixedClock { FixedClock(frozen: frozenNow) } } // ═══════════════════════════════════════════════════════════════════════════ // Part 6 — Resources: unique handles behind capabilities // ═══════════════════════════════════════════════════════════════════════════ // // The journal is an I/O handle: a duplicated one is a bug, so it is // noncopyable and lives in a UniqueBox. There is no database capability in // this app — the World is the truth; the journal of commands IS persistence. // Access is `mutate`, because appending to a log is a write and the typing // should say so. struct WriteAheadLog: ~Copyable { enum Entry: Codable { case settled(Receipt) } private var fd: Int32 // owned file descriptor — the uniqueness motive init(path: String) { fd = 3 /* open(path, ...) elided */ } mutating func append(_ entry: Entry) { /* write + fsync elided */ } deinit { /* close(fd) */ } } protocol HasJournal: ~Copyable { var journal: WriteAheadLog { mutate } // [6.4] } // ═══════════════════════════════════════════════════════════════════════════ // Part 7 — The World: the composition root // ═══════════════════════════════════════════════════════════════════════════ // // THE PERIMETER RULE: this is the only tier of the program allowed to name // `KioskWorld`. Everything above is parametric and therefore scoped; a // function that names the concrete World holds ambient authority over every // slice. In a real project, Parts 1–6 live in modules that cannot import // this one, making the rule a build error instead of a convention. // // Reading the declaration top to bottom: the conformance list is the entire // production link map; the stored slices witness the capabilities directly // (a stored property satisfies a borrow/mutate requirement in place); the // UniqueBox makes the whole struct noncopyable — which is what forces // snapshots through the copyable slices in Part 8. struct KioskWorld: ~Copyable, HasMutableMenu, HasMutableTabs, HasMutableStock, LinkStripePayment, LinkSystemClock, HasJournal { // State — copyable slices, the write-conflict map of the app var menu = MenuState() var tabs = TabsState() var stock = StockState() var stripeSession = StripeSession() // demanded by LinkStripePayment // Resources — unique, noncopyable private var journalBox: UniqueBox<WriteAheadLog> // [6.4] var journal: WriteAheadLog { borrow { journalBox.value } // [6.4] mutate { &journalBox.value } // [6.4] } init(journalPath: String) { journalBox = UniqueBox(WriteAheadLog(path: journalPath)) } } // ═══════════════════════════════════════════════════════════════════════════ // Part 8 — Snapshots: undo as an assignment // ═══════════════════════════════════════════════════════════════════════════ // // The slices are Codable plain data; the World cannot be copied. So "the // state of the app" is *necessarily* this struct — the type system closed // every other door. Undo, optimistic rollback, golden tests, and crash // payloads are all uses of the same four-line value. struct KioskSnapshot: Codable, Equatable { var menu: MenuState var tabs: TabsState var stock: StockState // stripeSession deliberately excluded: gateway state is not domain truth, // and restoring it would desynchronize us from the processor. Snapshot // membership is a per-slice decision, made here, visibly. } extension KioskWorld { var snapshot: KioskSnapshot { KioskSnapshot(menu: menu, tabs: tabs, stock: stock) } mutating func restore(_ s: KioskSnapshot) { (menu, tabs, stock) = (s.menu, s.tabs, s.stock) } } // ═══════════════════════════════════════════════════════════════════════════ // Part 9 — The shell: one actor owns the World // ═══════════════════════════════════════════════════════════════════════════ // // The World never leaves this actor, so it never needs to be Sendable, so // nothing inside it needs locks. Async work (real card networks, sync to a // server) runs OFF the actor, produces Sendable plain values, and re-enters // as a command — the World itself stays synchronous. @MainActor @Observable final class KioskShell { private(set) var world: KioskWorld private var undoStack: [KioskSnapshot] = [] init(world: consuming KioskWorld) { self.world = world } func settle(_ tab: Tab.ID) { undoStack.append(world.snapshot) // checkpoint: one assignment do { _ = try world.settle(tab) } catch { world.restore(undoStack.removeLast()) // rollback: one assignment // surface `error` to UI ... } } func undo() { guard let s = undoStack.popLast() else { return } world.restore(s) } // UI reads through borrow-constrained projections — e.g. a SwiftUI view // observing `Observations { shell.board }` — and is unable to mutate. var board: String { renderBoard(world) } } // ═══════════════════════════════════════════════════════════════════════════ // Part 10 — Tests: substitution and literal state // ═══════════════════════════════════════════════════════════════════════════ // // The test world swaps exactly two Links — payment and clock — and supplies // no journal file. Everything else is the same code, re-verified complete by // the compiler. "Given" clauses are literals on slices, not mock setup. import Testing struct TestWorld: ~Copyable, HasMutableMenu, HasMutableTabs, HasMutableStock, LinkMockPayment, LinkFixedClock, HasJournal { var menu = MenuState() var tabs = TabsState() var stock = StockState() let frozenNow = Date(timeIntervalSince1970: 1_750_000_000) private var journalBox = UniqueBox(WriteAheadLog(path: "/dev/null")) // [6.4] var journal: WriteAheadLog { borrow { journalBox.value } // [6.4] mutate { &journalBox.value } // [6.4] } } @Test func settlingDecrementsStockAndChargesOnce() throws { var world = TestWorld() let latte = Item.ID(raw: 1) world.menu.items[latte] = Item(name: "Latte", price: 5, recipe: [.beans: 1, .milk: 2, .cup: 1]) world.stock.units = [.beans: 10, .milk: 10, .cup: 10] let tab = world.openTab(for: "Ada") try world.add(latte, to: tab) let receipt = try world.settle(tab) #expect(receipt.total == 5) #expect(receipt.reference == "mock_ok") #expect(receipt.chargedAt == world.frozenNow) // clock is linked, not ambient #expect(world.stock.units[.milk] == 8) #expect(world.tabs.open[tab]?.settled == true) } @Test func shortageRollsBackCleanly() throws { var world = TestWorld() let latte = Item.ID(raw: 1) world.menu.items[latte] = Item(name: "Latte", price: 5, recipe: [.milk: 2]) world.stock.units = [.milk: 2] let tab = world.openTab(for: "Grace") try world.add(latte, to: tab) let checkpoint = world.snapshot // given: a literal copy world.stock.units[.milk] = 1 // sabotage after the add #expect(throws: StockState.Shortage.self) { try world.settle(tab) } world.restore(checkpoint) // rollback: one assignment #expect(world.snapshot == checkpoint) // golden-state equality } // ═══════════════════════════════════════════════════════════════════════════ // Coda — what to notice on a second read // ═══════════════════════════════════════════════════════════════════════════ // // • Grep `HasMutableStock`: you have just enumerated every writer of stock // in the program. That query is the architecture. // • Parts 1–6 never mention KioskWorld. Delete Part 7 and they still // compile against TestWorld alone — the perimeter is real. // • The two test doubles required zero mocking framework: one protocol // conformance (MockGateway) and one literal (frozenNow). // • Nothing here allocates, locks, or dispatches dynamically once // specialized; `@specialized(where Self == KioskWorld)` on `settle` and // `@inline(always)` on the projections pin that down on hot paths.