Distributed actor isolation
Distributed actor isolation êŽë š
Available from Swift 5.7
SE-0336 (apple/swift-evolution
) and SE-0344 (apple/swift-evolution
) introduce the ability for actors to work in a distributed form â to read and write properties or call methods over a network using remote procedure calls (RPC).
This is every part as complicated a problem as you might imagine, but there are three things to make it easier:
- Swiftâs approach of location transparency effectively forces us to assume the actors are remote, and in fact provides no way of determining at compile time whether an actor is local or remote â we just use the same
await
calls we would no matter what, and if the actor happens to be local then the call is handled as a regular local actor function. - Rather than forcing us to build our own actor transport systems, Apple is providing a ready-made implementation (
apple/swift-distributed-actors
) for us to use. Apple has said they âonly expect a handful of mature implementations to take the stage eventually,â but helpfully all the distributed actor features in Swift are agnostic of whatever actor transport you use. - To move from an actor to a distributed actor we mostly just need to write
distributed actor
thendistributed func
as needed.
So, we can write code like this to simulate someone tracking a trading card system:
// use Apple's ClusterSystem transport
typealias DefaultDistributedActorSystem = ClusterSystem
distributed actor CardCollector {
var deck: Set<String>
init(deck: Set<String>) {
self.deck = deck
}
distributed func send(card selected: String, to person: CardCollector) async -> Bool {
guard deck.contains(selected) else { return false }
do {
try await person.transfer(card: selected)
deck.remove(selected)
return true
} catch {
return false
}
}
distributed func transfer(card: String) {
deck.insert(card)
}
}
Because of the throwing nature of distributed actor calls, we can be sure itâs safe to remove the card from one collector if the call to person.transfer(card:)
didnât throw.
Swiftâs goal is that you can transfer your knowledge of actors over to distributed actors very easily, but there are some important differences that might catch you out.
First, all distributed functions must be called using try
as well as await
even if the function isnât marked as throwing, because itâs possible for a failure to happen as a result of the network call going awry.
Second, all parameters and return values for distributed methods must conform to a serialization process of your choosing, such as Codable
. This gets checked at compile time, so Swift can guarantee itâs able to send and receive data from remote actors.
And third, you should consider adjusting your actor API to minimize data requests. For example, if you want to read the username
, firstName
, and lastName
properties of a distributed actor, you should prefer to request all three with a single method call rather than requesting them as individual properties to avoid potentially having to go back and forward over the network several times.