Protocol conformances crossing into actor-isolated code (ConformanceIsolation)
Overview
Protocol conformances crossing into actor-isolated code can cause data races in your program. Resolve this error by ensuring access to isolated state is always done within the actor.
When a type conforms to a protocol, any generic code can perform operations on that type through the protocol. If the operations that the type used to satisfy the protocol requirements are actor-isolated, this may result in a diagnostic indicating that the conformance crosses into actor-isolated code. For example:
protocol P {
func f()
}
@MainActor
struct MyData: P {
func f() { }
}This code will produce an error similar to:
@MainActor
struct MyData: P {
// |- error: conformance of 'MyData' to protocol 'P' crosses into main actor-isolated code and can cause data races
// |- note: isolate this conformance to the main actor with '@MainActor'
// `- note: turn data races into runtime errors with '@preconcurrency'
func f() { }
// |- note: main actor-isolated instance method 'f()' cannot satisfy nonisolated requirement
// `- note: mark instance method 'f()' 'nonisolated'
}There are several options for resolving this error, as indicated by the notes:
If all of the operations used to satisfy the protocol’s requirements are on the same global actor (such as the main actor), the conformance itself can be isolated to that global actor. This allows the conformance to be used inside code running on that actor, but it cannot be used concurrently. A conformance can be isolated to a global actor the same way as anything else in the language, e.g.,
@MainActor struct MyData: @MainActor P { func f() { } }If the protocol requirements themselves are meant to always be used from the correct isolation domain (for example, the main actor) but the protocol itself did not describe that requirement, the conformance can be marked with
@preconcurrency. This approach moves isolation checking into a run-time assertion, which will produce a fatal error if an operation is called without already being on the right actor. A@preconcurrencyconformance can be written as follows:@MainActor struct MyData: @preconcurrency P { func f() { } }If the conformance needs to be usable anywhere, then each of the operations used to satisfy its requirements must be marked
nonisolated. This means that they will not have access to any actor-specific operations or state, because these operations can be called concurrently from anywhere. The result would look like this:@MainActor struct MyData: P { nonisolated func f() { } }
Stored Properties
When conforming to a protocol that has read only properties, there is an additional option; defining properties as nonisolated let. This makes them immutable and concurrently accessible. For example:
@MainActor
class MyModel: ObservableObject, Identifiable {
var id = UUID()
// |- note: main actor-isolated property 'id' cannot satisfy nonisolated requirement
// `- note: change property 'id' to a 'nonisolated let' constant
var count = 0
}By changing id from var to nonisolated let, any generic code that works with Identifiable can safely read id from any isolation region:
@MainActor
class MyModel: ObservableObject, Identifiable {
nonisolated let id = UUID()
var count = 0
}See Also
@dynamicCallable implementation requirements (DynamicCallable)Add @preconcurrency import (AddPreconcurrencyImport)Always enabled availability domains (AlwaysAvailableDomain)Argument matching for trailing closures (TrailingClosureMatching)Calling a mutating async actor-isolated method (ActorIsolatedMutatingAsync)Calling an actor-isolated method from a synchronous nonisolated context (ActorIsolatedCall)Captures in a `@Sendable` closure (SendableClosureCaptures)Compilation caching (CompilationCaching)Conforming to `StringInterpolationProtocol` (StringInterpolationConformance)Conversion from `@isolated(any)` function type to synchronous function type (ConversionFromIsolatedAnyToSynchronous)Cross-isolation data race (RegionIsolationCrossIsolationDataRace)Deprecated declaration warnings (DeprecatedDeclaration)Deprecated implementation-only imports (ImplementationOnlyDeprecated)Dynamic exclusivity (DynamicExclusivity)Embedded Swift language restrictions (EmbeddedRestrictions)