Swift Concurrency Notes
Notes reading the async/concurrency proposals to be introduced in Swift 5.5. Before you can play chess well, you must first learn the rules of chess.
Concurrency Roadmap
- Glossary of terms
SE-0296: Async/Await
- This proposal is about asynchrony but doesn’t talk about concurrency.
- Built on the idea of continuations, a generalized subroutine that can be suspended and resume. See Coroutine Representations and ABIs in LLVM by John McCall.
- Avoids the control complexity of using callbacks and lets you use control structures found in normal synchronous code.
- Potential suspension points are marked explicitly. This allows you to reason about potential interleaving.
- Init can be async. Deinit cannot be async.
- An async throwing function type can accept a non-throwing sync function. Async throwing functions are all-mighty.
- Potential suspension points must be marked with await and must occur in an async context.
- A single await can handle multiple expressions that are potential suspension points.
- Normally it is “try await …” but you can reverse it with parenthesis “await (try …)”
- Closures can type infer async closures if you use an await in the closure. (Though the inference does not propagate to outer closures.)
- Overloads must differ more than just async. Overload resolution uses the context to decide what function to call.
- A function that takes an async autoclosure must be async itself. (Future possible direction reasync.)
- An a async function cannot fulfill a sync protocol requirement.
Main example:
func processImageData() async throws -> Image {
let dataResource = try await loadWebResource("dataprofile.txt")
let imageResource = try await loadWebResource("imagedata.dat")
let imageTmp = try await decodeImage(dataResource, imageResource)
let imageResult = try await dewarpAndCleanupImage(imageTmp)
return imageResult
}
SE-302: Sendable and @Sendable closures
Sendable is used as the primary currency that gets passed between concurrency domains. Actors can be thought of message sending domains that use mailbox messages. Sendable data and @Sendable functions are the payloads being sent.
- Sendable is a “marker protocol” (implemented specially in the compiler for now)
- Many ways to handle concurrency. Swift wants to be a principled system that is sound and easy to use.
- Want a simple model that lasts many decades into the future.
- Built with value semantic types.
- Actors are islands of concurrency and Sendable lets you transfer across concurrency domains.
- Standard type such as Int and String are Sendable.
- Many structs and enums get Sendable conformance for free if all of their sub-types conform. (Public non-frozen structs and enums don’t get it)
- Actors are Sendable implicitly
- Use @unchecked Sendable as an escape hatch.
- You will get weird behavior if your type is marked Sendable but is actually not. (Much like implementing Hash requirements incorrectly.)
- The type @Sendable is an attribute on functions
- Error types must conform to Sendable
Standard library types:
extension Optional: Sendable where Wrapped: Sendable {}
extension Array: Sendable where Element: Sendable {}
extension Dictionary: Sendable
where Key: Sendable, Value: Sendable {}
- ManagedBuffer must not conform to Sendable (even unsafely)
- The
Unsafe**Pointer
pointer family are Sendable but you have to be careful not to mutate from the parent