top of page

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

  1. Read the error as a question about ownership: who owns this value, and where does it run?
  2. Fix by moving state to its owner, making values Sendable, or marking the caller with the right isolation.
  3. Never add @unchecked Sendable or nonisolated(unsafe) to make an error disappear.
  4. 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 context

Fix: 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 state

Fix: 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 actor

Fix: 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 mutable

Fix: 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 races

Fix: 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 closure

Fix: 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.

Links

bottom of page