Immutable data is a good fit for values passed across concurrency boundaries.

Pass immutable data

count
sendable_values.swift
Replay: real traced execution (multi-file project)
struct Job: Sendable {
    let name: String
    let count: Int
}

func summarize(_ job: Job) async -> String {
    return "\(job.name):\(job.count)"
}

let count = 2
let job = Job(name: "trace", count: count)
let message = await summarize(job)

print(message)
struct Job: Sendable {
    let name: String
    let count: Int
}

func summarize(_ job: Job) async -> String {
    return "\(job.name):\(job.count)"
}

let count = 4
let job = Job(name: "trace", count: count)
let message = await summarize(job)

print(message)
struct Job: Sendable {
    let name: String
    let count: Int
}

func summarize(_ job: Job) async -> String {
    return "\(job.name):\(job.count)"
}

let count = 6
let job = Job(name: "trace", count: count)
let message = await summarize(job)

print(message)
  1. count ← 2, job ← Job(name: "trace", count: 2)

    10let count→ 2 = 2  //@count=4, 611let job→ Job(name: "trace", count: 2) = Job(name: "trace", count: count2)12let message = await summarize(jobJob(name: "trace", count: 2))
  2. func summarize(_ job: Job) async -> String

    6func summarize(_ jobJob(name: "trace", count: 2): Job) async -> String {7    return "\(job.nametrace):\(job.count2)"8}
  3. message ← trace:2

    11let job = Job(name: "trace", count: count)12let message→ trace:2 = await summarize(jobJob(name: "trace", count: 2))1314print(messagetrace:2)
    outputtrace:2
  1. count ← 4, job ← Job(name: "trace", count: 4)

    10let count→ 4 = 411let job→ Job(name: "trace", count: 4) = Job(name: "trace", count: count4)12let message = await summarize(jobJob(name: "trace", count: 4))
  2. func summarize(_ job: Job) async -> String

    6func summarize(_ jobJob(name: "trace", count: 4): Job) async -> String {7    return "\(job.nametrace):\(job.count4)"8}
  3. message ← trace:4

    11let job = Job(name: "trace", count: count)12let message→ trace:4 = await summarize(jobJob(name: "trace", count: 4))1314print(messagetrace:4)
    outputtrace:4
  1. count ← 6, job ← Job(name: "trace", count: 6)

    10let count→ 6 = 611let job→ Job(name: "trace", count: 6) = Job(name: "trace", count: count6)12let message = await summarize(jobJob(name: "trace", count: 6))
  2. func summarize(_ job: Job) async -> String

    6func summarize(_ jobJob(name: "trace", count: 6): Job) async -> String {7    return "\(job.nametrace):\(job.count6)"8}
  3. message ← trace:6

    11let job = Job(name: "trace", count: count)12let message→ trace:6 = await summarize(jobJob(name: "trace", count: 6))1314print(messagetrace:6)
    outputtrace:6
sendable value A simple `Sendable` struct communicates that its stored values are safe to pass between concurrent tasks.