Swift actors and structured concurrency tasks provide a mechanism for isolating state in concurrent programs to eliminate data races. When and how do we allow data to be transferred between concurrency domains? Such transfers occur in arguments and results of actor method calls, or tasks created by structured concurrency.
Let’s take a look at some common cases that are safe to transfer:
However, everything isn’t simple here. What about types contain general class references, closures that capture mutable state, and other non-value types. We need a way to differentiate between the cases that are safe to transfer and those that are not.
Swift introduces Sendable as a marker protocol, which indicates that the protocol has some semantic property but is entirely a compile-time notion that does not have any impact at runtime. Marker protocols have the following restrictions:
is
or as?
checks.The Sendable protocol models types that are allowed to be safely passed across concurrency domains by copying the value. This includes value-semantic types, references to immutable reference types, internally synchronized reference types, @Sendable closures, and potentially other future type system extensions for unique ownership etc.
Unless a type implicitly conforms to Sendable in some cases, you need to declare conformance to Sendable explicitly in the same file as the type’s declaration.
final class Movie: Sendable {
let formattedReleaseDate = "2022"
}
let hello = { @Sendable (name: String) -> String in
return "Hello" + name
}
func runLater(_ function: @escaping @Sendable () -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 3, execute: function)
}
func globalFunction(arr: [Int]) {
var state = 42
@Sendable
func mutateLocalState2(value: Int) {
// Error: 'state' is captured as a let because of @Sendable
state += value
}
}
// Implicitly conforms to Sendable
struct Foo<T: Sendable> {
var value: T
}
// Not implicitly conform to Sendable because T does not conform to Sendable
struct Foo<T> {
var value: T
}
To declare conformance to Sendable without any compiler enforcement, write @unchecked Sendable
. You are responsible for the correctness of unchecked sendable types, for example, by protecting all access to its state with a lock or a queue. Unchecked conformance to Sendable also disables enforcement of the rule that conformance must be in the same file.
class Foo: @unchecked Sendable {
// implementation unchanged
}