Swift 6.4: You Can Finally await Inside defer
SE-0493 lifts the old “no await in defer” rule. In an async function, the cleanup can now be async too:
func sync(with server: Server) async throws {
let session = try await server.openSession()
defer { await session.close() }
try await session.upload(pendingChanges)
try await session.download(remoteChanges)
}
Before this, you had two bad options. You could copy await session.close() onto every exit path, including each throw and early return, and hope nobody adds a new one later. Or you could write defer { Task { await session.close() } }, which starts an unstructured task that outlives the function, runs at some later point, and isn’t guaranteed to finish before the next session opens.
How it works:
- The
deferbody is awaited implicitly when the scope exits. You only writeawaiton the calls inside it, not on thedeferitself. - The function suspends only at the
awaits written inside the body, so a synchronousdeferbehaves exactly as before. - It only works in an
asynccontext.defer { await g() }in a sync function is still a compile error. Inside a closure, it makes the closure inferred asasync. - Cancellation gets no special treatment. If the task was cancelled (which is often why you’re exiting), the awaited cleanup runs in that cancelled task. Any cancellation-aware call inside it, like
Task.sleep, aURLSessionrequest or atry Task.checkCancellation(), can bail out early.
Takeaway: async cleanup goes in defer now, not in a Task { } inside defer. Keep in mind that a cancelled task stays cancelled inside defer, so cleanup that must finish needs a cancellation shield (next week).