What’s Coming in Swift 6.4 – Wade Tregaskis
Skip to content
Contents<br>Async inside defer<br>Task cancellation shields<br>Async Result Support<br>More standard library types support ~Escapable and ~Copyable<br>Private member variables with default values are excluded (by default) from compiler-generated initialisers<br>Borrow and Mutate Accessors<br>withTemporaryAllocation using OutputSpan / OutputRawSpan<br>~Sendable<br>Compiler warning for silently swallowing errors inside a Task<br>isTriviallyIdentical<br>Hashable conformance for UnownedTaskExecutor, Dictionary.Keys, CollectionOfOne, and EmptyCollection<br>Ref and MutableRef types<br>Array expression trailing closures<br>The demangle function<br>Software Bill of Materials Generation
This is a summary of the Swift Evolution Proposals that are marked as Implemented in 6.4. Note that I have not personally verified their implementation.
Async inside defer
SE-493
func f() async {<br>await setUp()<br>defer {<br>await performAsyncTeardown()
try doSomething()<br>The enclosing context – the function "f" in the above example – must be async, of course. Type inference will work in closures, too; they’ll implicitly be async if they contain any awaits in defers.
// 'f' implicitly has type '() async -> ()'<br>let f = {<br>defer { await g() }<br>Cautions:
Use of async in defer adds additional suspension points to the end of the function. Though this is not much different from the existing hazard that defer introduces, in placing code obliquely at the end of the function.
Task cancellation works like normal inside defer, so care needs to be taken that clean-up actually happens and isn’t short-circuited. Again, this is just the same as it’s always been for non-async code inside defer (though async code is more likely to check for and react to cancellation). But, it can be guarded against using…
Task cancellation shields
SE-504
This allows you to hide task cancellation status within a block (and stop automatic propagation of cancellation to subtasks). This is particularly helpful for clean-up code that wants to ensure it actually runs, even if it calls general helper functions that normally do observe cancellation.
func doWork() {<br>while !Task.isCancelled {<br>let result = crunchNumbers()<br>await send(.latestResult, result)
// Clean up<br>await send(.goodbye)
func send(_ type: MessageType, _ payload: Data? = nil) {<br>guard !Task.isCancelled else {<br>return
The ‘goodbye’ message in that example would never actually be sent, and worse that failure is completely silent (because you typically don’t want to log "Cancelled!" everywhere, as it’s very noisy in what may be a relatively common case – depending on how you & your dependencies use task cancellation).
With Swift 6.4 you can write:
func doWork() {<br>while !Task.isCancelled {<br>let result = crunchNumbers()<br>await send(.latestResult, result)
// Clean up<br>withTaskCancellationShield {<br>await send(.goodbye)
func send(_ type: MessageType, _ payload: Data? = nil) {<br>guard !Task.isCancelled else {<br>return
send will read false from Task.isCancelled (as would any children, function- or structured-task-wise) and thus actually send the goodbye now.
Caution:
Hiding cancellation status from arbitrary code might lead to unbounded delays in cancellation completing, because the code under the shield sees no reason to finish promptly. Cancellation usually has some degree of time-sensitivity, e.g. if a user cancelled the task – or asked the program to quit, cancelling any work in progress – then they might tolerate a few seconds of delay, but won’t be pleased if the program just "hangs" (takes so long they start questioning whether it’s actually cancelling at all).
Since cancellation propagation to subtasks is blocked while the shield is in place, you may need to do extra work to ensure subtasks are appropriately cancelled (e.g. if you exit the cancellation shield ‘early’, you probably do want the subtasks to then cancel too – but you’ll have to explicitly cancel them yourself).
Conversely, task cancellation shields do not block explicit cancellation of subtasks, so you must be careful wherever you have cancellation handlers or code that might show a little too much initiative in cancelling tasks.
Async Result Support
SE-530
Now there’s a way to initialise a Result from an async closure, through a new initialiser. It works exactly like you’d expect:
let result = await Result {<br>try await asyncWork()<br>More standard library types support ~Escapable and ~Copyable
SE-499
As of Swift 6.4, these protocols will no longer require the conforming types to be Copyable and Escapable:
Equatable
Comparable
Hashable
CustomStringConvertible
CustomDebugStringConvertible
TextOutputStream
TextOutputStreamable
Additionally, LosslessStringConvertible no longer requires Copyable, and both Optional and Result now support ~Copyable and ~Escapable elements. The latter is an especially big improvement for users of ~Copyable or ~Escapable types, given how fundamental (and nice!) Optional is to idiomatic...