Sendable and Swift 6 Data-Race Safety

36.1 The Sendable Protocol

A type is Sendable if values can be safely shared across concurrency domains (tasks/actors).

Value types whose stored properties are all Sendable ✅ (implicitly, for non-public types)

Immutable final class with let Sendable properties ✅ (declare : Sendable)

❌ (unless internally synchronized: @unchecked Sendable with locks/Mutex)

@Sendable closures capture only Sendable values

struct Message: Sendable { let id: Int; let text: String }

final class Config: Sendable {
    let apiKey: String
    init(apiKey: String) { self.apiKey = apiKey }
}

final class Counter: @unchecked Sendable {       // you promise thread safety
    private let lock = NSLock()
    private var value = 0
    func increment() { lock.withLock { value += 1 } }
}

func run(_ work: @escaping @Sendable () async -> Void) { Task { await work() } }

36.2 Swift 6 Language Mode: Complete Data-Race Safety

In the Swift 6 language mode, data races are compile-time errors. The compiler checks isolation boundaries, Sendable conformance, and global mutable state.

Region-based isolation (SE-0414) and sending parameters (SE-0430) let non-Sendable values be passed across boundaries when the compiler proves they're no longer used by the sender.

var globalCounter = 0                    // ❌ Swift 6: global mutable state isn't concurrency-safe
nonisolated(unsafe) var legacyCounter = 0     // opt out (you guarantee safety)
let safeGlobal = 42                      // ✅ immutable
@MainActor var uiState = 0               // ✅ isolated to the MainActor
func makeReport() async -> Report {
    let report = Report()                // non-Sendable class
    await uploader.upload(report)        // OK if `report` isn't used afterward (region transfer)
    return Report()
}

36.3 Migration Strategy

Enable warnings first: -strict-concurrency=complete (Swift 5 mode), or upcoming feature flags in Package.swift.

Annotate UI code with @MainActor. Make models value types/Sendable. Convert shared mutable state to actors or Mutex-protected types.

Use @preconcurrency import for libraries not yet updated. Migrate module by module.

Switch to Swift 6 mode (swiftLanguageModes: [.v6] / Xcode build setting). Consider 6.2's approachable concurrency defaults for app targets.