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

SE-0304: Structured concurrency