CFA Reference: Troubleshooting Swift 6 Compiler Errors
Formal CFA AI Toolkit for iOS Documentation · Reference 18 of 26
Swift 6 language mode turns data-race risks into errors. Each error points to an ownership or isolation problem; CFA fixes the ownership rather than silencing the compiler.
Definitions
- Isolation context: Where code runs: the main actor, another actor, or nonisolated.
- Sendable: Safe to pass between isolation contexts.
- Region-based isolation: Analysis that reports when a non-Sendable value is sent to another context and used again.
- Escape hatch: @unchecked Sendable or nonisolated(unsafe); not a CFA fix.
Rules
- Read the error as a question about ownership: who owns this value, and where does it run?
- Fix by moving state to its owner, making values Sendable, or marking the caller with the right isolation.
- Never add @unchecked Sendable or nonisolated(unsafe) to make an error disappear.
- Re-run the build with warnings as errors after each fix.
Decision rules
- When a nonisolated function calls a @MainActor Feature, mark the caller @MainActor or make it async and await the call.
- When a static var is reported as shared mutable state, move the state into its Feature or an actor, or make it a let.
- When an actor property is read from the main actor, ask the actor through an async method and await it.
- When a class conforming to a Sendable protocol has var properties, make it an actor.
- When the compiler says sending a value risks a data race, pass a Sendable value, such as a struct, instead of a shared class instance.
- When a concurrent closure mutates a captured var, return values from child tasks and combine them in the parent.
Example
Each diagnostic below is the real output from compiling the snippet with -swift-version 6 on the Swift 6.2 and 6.4 Linux toolchains (the wording is identical on both unless noted). The “sending” diagnostics appear when the compiler runs its data-race analysis, so they show up in a full build, not in -typecheck alone.
1. Calling a @MainActor Feature from a nonisolated function
Snippet
import Observation
@MainActor @Observable final class SettingsFeature { func reset() {} }
func resetEverything(settingsFeature: SettingsFeature) { settingsFeature.reset() }Compiler output
error: call to main actor-isolated instance method 'reset()' in a synchronous nonisolated contextFix: mark resetEverything(settingsFeature:) @MainActor, or move the call into a ViewModel, which is already @MainActor.
2. A mutable static property
Snippet
struct Cache { static var sharedNames: [String] = [] }Compiler output
error: static property 'sharedNames' is not concurrency-safe because it is nonisolated global shared mutable stateFix: give the data one owner. Store it as private(set) state on a Feature, or inside an actor repository.
3. Reading actor state from the main actor
Snippet
actor LocalHabitsRepository { var storedNames: [String] = [] }
@MainActor func firstName(repository: LocalHabitsRepository) -> String? { repository.storedNames.first }Compiler output
error: actor-isolated property 'storedNames' can not be referenced from the main actorFix: add an async method on the actor, such as fetchHabits(), and await it from the Feature.
4. A class with mutable state conforming to a Sendable protocol
Snippet
protocol HabitsRepository: Sendable { func fetchNames() async throws -> [String] }
final class MemoryRepository: HabitsRepository {
var names: [String] = []
func fetchNames() async throws -> [String] { names }
}Compiler output
error: stored property 'names' of 'Sendable'-conforming class 'MemoryRepository' is mutableFix: make the repository an actor, as LocalHabitsRepository is in Reference 6.
5. Sending a non-Sendable class to an actor
Snippet
final class Draft { var text = "" }
actor NotesRepository { func save(_ draft: Draft) {} }
@MainActor func save(draft: Draft, repository: NotesRepository) async {
await repository.save(draft)
draft.text = "changed"
}Compiler output
error: sending 'draft' risks causing data racesFix: make Draft a Sendable struct and pass a value; the Feature keeps ownership of the editable copy.
6. Mutating a captured variable in concurrent child tasks
Snippet
func countAll() async {
var total = 0
await withTaskGroup(of: Void.self) { group in
group.addTask { total += 1 }
}
print(total)
}Compiler output (wording differs between versions)
Swift 6.4:
error: passing closure as a 'sending' parameter risks causing data races between code in the current isolation context and concurrent execution of the closure
Swift 6.2:
error: passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closureFix: return a value from each child task and add the results in the parent, for example with withTaskGroup(of: Int.self) and a for await loop.
Common mistakes
- Adding @unchecked Sendable to a class with mutable state.
- Wrapping the call in DispatchQueue.main.async.
- Wrapping the call in Task { } without an owner, which hides the problem and loses ordering.
- Turning off Swift 6 language mode for a file.
