Skip to content

Commit a851866

Browse files
authored
Merge pull request #60296 from apple/wip-distributed-docs
[Distributed] More API documentation
2 parents ad938a9 + 49ae51c commit a851866

File tree

2 files changed

+492
-38
lines changed

2 files changed

+492
-38
lines changed

stdlib/public/Distributed/DistributedActor.swift

Lines changed: 235 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,185 @@ import _Concurrency
1717

1818
/// Common protocol to which all distributed actors conform implicitly.
1919
///
20-
/// It is not possible to conform to this protocol manually explicitly.
21-
/// Only a 'distributed actor' declaration or protocol with 'DistributedActor'
22-
/// requirement may conform to this protocol.
20+
/// The `DistributedActor` protocol generalizes over all distributed actor types.
21+
/// Distributed actor types implicitly conform to this protocol.
2322
///
24-
/// The 'DistributedActor' protocol provides the core functionality of any
25-
/// distributed actor.
23+
/// It is not possible to conform to this protocol by any other declaration other than a `distributed actor`.
2624
///
27-
/// ## Implicit `Codable` conformance
28-
/// If created with an actor system whose `ActorID` is `Codable`, the
25+
/// It is possible to require a type to conform to the
26+
/// ``DistributedActor`` protocol by refining it with another protocol,
27+
/// or by using a generic constraint.
28+
///
29+
/// ## Synthesized properties
30+
/// For every concrete distributed actor declaration, the compiler synthesizes two properties: `actorSystem` and `id`.
31+
/// They witness the ``actorSystem`` and ``id`` protocol requirements of the ``DistributedActor`` protocol.
32+
///
33+
/// It is not possible to implement these properties explicitly in user code.
34+
/// These properties are `nonisolated` and accessible even if the instance is _remote_,
35+
/// because _all_ distributed actor references must store the actor system remote calls
36+
/// will be delivered through, as well as the id identifying the target of those calls.
37+
///
38+
/// ## The ActorSystem associated type
39+
/// Every distributed actor must declare what type of actor system
40+
/// it is part of by implementing the ``ActorSystem`` associated type requirement.
41+
///
42+
/// This causes a number of other properties of the actor to be inferred:
43+
/// - the ``SerializationRequirement`` that will be used at compile time to
44+
/// verify `distributed` target declarations are well formed,
45+
/// - if the distributed actor is `Codable`, based on the ``ID`` being Codable or not,
46+
/// - the type of the ``ActorSystem`` accepted in the synthesized default initializer.
47+
///
48+
/// A distributed actor must declare what type of actor system it is ready to
49+
/// work with by fulfilling the ``ActorSystem`` type member requirement:
50+
///
51+
/// ```swift
52+
/// distributed actor Greeter {
53+
/// typealias ActorSystem = GreetingSystem // which conforms to DistributedActorSystem
54+
///
55+
/// func greet() -> String { "Hello!" }
56+
/// }
57+
/// ```
58+
///
59+
/// ### The DefaultDistributedActorSystem type alias
60+
/// Since it is fairly common to only be using one specific type of actor system
61+
/// within a module or entire codebase, it is possible to declare the default type
62+
/// of actor system all distributed actors will be using in a module by declaring
63+
/// a `DefaultDistributedActorSystem` module wide typealias:
64+
///
65+
/// ```swift
66+
/// import Distributed
67+
/// import AmazingActorSystemLibrary
68+
///
69+
/// typealias DefaultDistributedActorSystem = AmazingActorSystem
70+
///
71+
/// distributed actor Greeter {} // ActorSystem == AmazingActorSystem
72+
/// ```
73+
///
74+
/// This declaration makes all `distributed actor` declarations
75+
/// that do not explicitly specify an ``ActorSystem`` type alias to assume the
76+
/// `AmazingActorSystem` as their `ActorSystem`.
77+
///
78+
/// It is possible for a specific actor to override the system it is using,
79+
/// by declaring an ``ActorSystem`` type alias as usual:
80+
///
81+
/// ```swift
82+
/// typealias DefaultDistributedActorSystem = AmazingActorSystem
83+
///
84+
/// distributed actor Amazing {
85+
/// // ActorSystem == AmazingActorSystem
86+
/// }
87+
///
88+
/// distributed actor Superb {
89+
/// typealias ActorSystem = SuperbActorSystem
90+
/// }
91+
/// ```
92+
///
93+
/// In general the `DefaultDistributedActorSystem` should not be declared public,
94+
/// as picking the default should be left up to each specific module of a project.
95+
///
96+
/// ## Default initializer
97+
/// While classes and actors receive a synthesized *argument-free default
98+
/// initializer* (`init()`), distributed actors synthesize a default initializer
99+
/// that accepts a distributed actor system the actor is part of: `init(actorSystem:)`.
100+
///
101+
/// The accepted actor system must be of the `Self.ActorSystem` type, which
102+
/// must conform to the ``DistributedActorSystem`` protocol. This is required
103+
/// because distributed actors are always managed by a concrete
104+
/// distributed actor system and cannot exist on their own without one.
105+
///
106+
/// It is possible to explicitly declare a parameter-free initializer (`init()`),
107+
/// however the `actorSystem` property still must be assigned a concrete actor
108+
/// system instance the actor shall be part of.
109+
///
110+
/// In general it is recommended to always have an `actorSystem` parameter as
111+
/// the last non-defaulted non-closure parameter in every actor's
112+
/// initializer parameter list. This way it is simple to swap in a "test actor
113+
/// system" instance in unit tests, and avoid relying on global state which could
114+
/// make testing more difficult.
115+
///
116+
/// ## Implicit properties
117+
/// Every concrete `distributed actor` type receives two synthesized properties,
118+
/// which implement the protocol requirements of this protocol: `id` and `actorSystem`.
119+
///
120+
/// ### Property: Actor System
121+
/// The ``actorSystem`` property is an important part of every distributed actor's lifecycle management.
122+
///
123+
/// Both initialization as well as de-initialization require interactions with the actor system,
124+
/// and it is the actor system that handles all remote interactions of an actor, by both sending
125+
/// or receiving remote calls made on the actor.
126+
///
127+
/// The ``actorSystem`` property must be assigned in every designated initializer
128+
/// of a distributed actor explicitly. It is highly recommended to make it a
129+
/// parameter of every distributed actor initializer, and simply forward the
130+
/// value to the stored property, like this:
131+
///
132+
/// ```swift
133+
/// init(name: String, actorSystem: Self.ActorSystem) {
134+
/// self.name = name
135+
/// self.actorSystem = actorSystem
136+
/// }
137+
/// ```
138+
///
139+
/// Forgetting to initialize the actor system, will result in a compile time error:
140+
///
141+
/// ```swift
142+
/// // BAD
143+
/// init(name: String, actorSystem: Self.ActorSystem) {
144+
/// self.name = name
145+
/// // BAD, will cause compile-time error; the `actorSystem` was not initialized.
146+
/// }
147+
/// ```
148+
///
149+
/// ### Property: Distributed Actor Identity
150+
/// ``id`` is assigned by the actor system during the distributed actor's
151+
/// initialization, and cannot be set or mutated by the actor itself.
152+
///
153+
/// ``id`` is the effective identity of the actor, and is used in equality checks,
154+
/// as well as the actor's synthesized ``Codable`` conformance if the ``ID`` type
155+
/// conforms to ``Codable``.
156+
///
157+
/// ## Automatic Conformances
158+
///
159+
/// ### Hashable and Identifiable conformance
160+
/// Every distributed actor conforms to the `Hashable` and `Identifiable` protocols.
161+
/// Its identity is strictly driven by its ``id``, and therefore hash and equality
162+
/// implementations directly delegate to the ``id`` property.
163+
///
164+
/// Comparing a local distributed actor instance and a remote reference to it
165+
/// (both using the same ``id``) always returns true, as they both conceptually
166+
/// point at the same distributed actor.
167+
///
168+
/// It is not possible to implement these protocols relying on the actual actor's
169+
/// state, because it may be remote and the state may not be available. In other
170+
/// words, since these protocols must be implemented using `nonisolated` functions,
171+
/// only `nonisolated` `id` and `actorSystem` properties are accessible for their
172+
/// implementations.
173+
///
174+
/// ### Implicit Codable conformance
175+
/// If created with an actor system whose ``DistributedActorSystem/ActorID`` is ``Codable``, the
29176
/// compiler will synthesize code for the concrete distributed actor to conform
30-
/// to `Codable` as well. This is necessary to support distributed calls where
31-
/// the `SerializationRequirement` is `Codable` and thus users may want to pass
32-
/// actors as arguments to remote calls.
33-
///
34-
/// The synthesized implementations use a single `SingleValueContainer` to
35-
/// encode/decode the `self.id` property of the actor. The `Decoder` required
36-
/// `init(from:)` is implemented by retrieving an actor system from the
37-
/// decoders' `userInfo`, effectively like this:
38-
/// `decoder.userInfo[.actorSystemKey] as? ActorSystem`. The obtained actor
39-
/// system is then used to `resolve(id:using:)` the decoded ID.
40-
///
41-
/// Use the `CodingUserInfoKey.actorSystemKey` to provide the necessary
177+
/// to ``Codable`` as well.
178+
///
179+
/// This is necessary to support distributed calls where the `SerializationRequirement`
180+
/// is ``Codable`` and thus users may want to pass actors as arguments to remote calls.
181+
///
182+
/// The synthesized implementations use a single ``SingleValueEncodingContainer`` to
183+
/// encode/decode the ``id`` property of the actor. The ``Decoder`` required
184+
/// ``Decoder/init(from:)`` is implemented by retrieving an actor system from the
185+
/// decoders' `userInfo`, effectively like as follows:
186+
///
187+
/// ```swift
188+
/// decoder.userInfo[.actorSystemKey] as? ActorSystem
189+
// ```
190+
///
191+
/// The such obtained actor system is then used to ``resolve(id:using:)`` the decoded ``ID``.
192+
///
193+
/// Use the ``CodingUserInfoKey/actorSystemKey`` to provide the necessary
42194
/// actor system for the decoding initializer when decoding a distributed actor.
195+
///
196+
/// - SeeAlso: ``DistributedActorSystem``
197+
/// - SeeAlso: ``Actor``
198+
/// - SeeAlso: ``AnyActor``
43199
@available(SwiftStdlib 5.7, *)
44200
public protocol DistributedActor: AnyActor, Identifiable, Hashable
45201
where ID == ActorSystem.ActorID,
@@ -58,17 +214,32 @@ public protocol DistributedActor: AnyActor, Identifiable, Hashable
58214
/// to return the same exact resolved actor instance, however all the references would
59215
/// represent logically references to the same distributed actor, e.g. on a different node.
60216
///
61-
/// Conformance to this requirement is synthesized automatically for any
62-
/// `distributed actor` declaration.
217+
/// Depending on the capabilities of the actor system producing the identifiers,
218+
/// the `ID` may also be used to store instance specific metadata.
219+
///
220+
/// ## Synthesized property
221+
///
222+
/// In concrete distributed actor declarations, a witness for this protocol requirement is synthesized by the compiler.
223+
///
224+
/// It is not possible to assign a value to the `id` directly; instead, it is assigned during an actors `init` (or `resolve`),
225+
/// by the managing actor system.
63226
nonisolated override var id: ID { get }
64227

65-
/// The `ActorSystem` that is managing this distributed actor.
228+
/// The ``DistributedActorSystem`` that is managing this distributed actor.
66229
///
67-
/// It is immutable and equal to the system passed in the local/resolve
68-
/// initializer.
230+
/// It is immutable and equal to the system assigned during the distributed actor's local initializer
231+
/// (or to the system passed to the ``resolve(id:using:)`` static function).
69232
///
70-
/// Conformance to this requirement is synthesized automatically for any
71-
/// `distributed actor` declaration.
233+
/// ## Synthesized property
234+
///
235+
/// In concrete distributed actor declarations, a witness for this protocol requirement is synthesized by the compiler.
236+
///
237+
/// It is required to assign an initial value to the `actorSystem` property inside a distributed actor's designated initializer.
238+
/// Semantically, it can be treated as a `let` declaration, that must be assigned in order to fully-initialize the instance.
239+
///
240+
/// If a distributed actor declares no initializer, its default initializer will take the shape of `init(actorSystem:)`,
241+
/// and initialize this property using the passed ``DistributedActorSystem``. If any user-defined initializer exists,
242+
/// the default initializer is not synthesized, and all the user-defined initializers must take care to initialize this property.
72243
nonisolated var actorSystem: ActorSystem { get }
73244

74245
/// Resolves the passed in `id` against the `system`, returning
@@ -81,6 +252,8 @@ public protocol DistributedActor: AnyActor, Identifiable, Hashable
81252
/// the system, allowing it to take over the remote messaging with the
82253
/// remote actor instance.
83254
///
255+
/// - Postcondition: upon successful return, the returned actor's ``id`` and ``actorSystem`` properties
256+
/// will be equal to the values passed as parameters to this method.
84257
/// - Parameter id: identity uniquely identifying a, potentially remote, actor in the system
85258
/// - Parameter system: `system` which should be used to resolve the `identity`, and be associated with the returned actor
86259
static func resolve(id: ID, using system: ActorSystem) throws -> Self
@@ -90,10 +263,17 @@ public protocol DistributedActor: AnyActor, Identifiable, Hashable
90263

91264
@available(SwiftStdlib 5.7, *)
92265
extension DistributedActor {
266+
267+
/// A distributed actor's hash and equality is implemented by directly delegating to its ``id``.
268+
///
269+
/// For more details see the "Hashable and Identifiable conformance" section of ``DistributedActor``.
93270
nonisolated public func hash(into hasher: inout Hasher) {
94271
self.id.hash(into: &hasher)
95272
}
96273

274+
/// A distributed actor's hash and equality is implemented by directly delegating to its ``id``.
275+
///
276+
/// For more details see the "Hashable and Identifiable conformance" section of ``DistributedActor``.
97277
nonisolated public static func ==(lhs: Self, rhs: Self) -> Bool {
98278
lhs.id == rhs.id
99279
}
@@ -102,12 +282,31 @@ extension DistributedActor {
102282
// ==== Codable conformance ----------------------------------------------------
103283

104284
extension CodingUserInfoKey {
285+
286+
/// Key which is required to be set on a `Decoder`'s `userInfo` while attempting
287+
/// to `init(from:)` a `DistributedActor`. The stored value under this key must
288+
/// conform to ``DistributedActorSystem``.
289+
///
290+
/// Forgetting to set this key will result in that initializer throwing, because
291+
/// an actor system is required in order to call ``DistributedActor/resolve(id:using:)`` using it.
105292
@available(SwiftStdlib 5.7, *)
106293
public static let actorSystemKey = CodingUserInfoKey(rawValue: "$distributed_actor_system")!
107294
}
108295

109296
@available(SwiftStdlib 5.7, *)
110297
extension DistributedActor /*: implicitly Decodable */ where Self.ID: Decodable {
298+
299+
/// Initializes an instance of this distributed actor by decoding its ``id``,
300+
/// and passing it to the ``DistributedActorSystem`` obtained from `decoder.userInfo[actorSystemKey]`.
301+
///
302+
/// ## Requires: The decoder must have the ``CodingUserInfoKey/actorSystemKey`` set to
303+
/// the ``ActorSystem`` that this actor expects, as it will be used to call ``DistributedActor/resolve(id:using:)``
304+
/// on, in order to obtain the instance this initializer should return.
305+
///
306+
/// - Parameter decoder: used to decode the ``ID`` of this distributed actor.
307+
/// - Throws: If the actor system value in `decoder.userInfo` is missing or mis-typed;
308+
/// the `ID` fails to decode from the passed `decoder`;
309+
// or if the ``DistributedActor/resolve(id:using:)`` method invoked by this initializer throws.
111310
nonisolated public init(from decoder: Decoder) throws {
112311
guard let system = decoder.userInfo[.actorSystemKey] as? ActorSystem else {
113312
throw DistributedActorCodingError(message:
@@ -122,6 +321,8 @@ extension DistributedActor /*: implicitly Decodable */ where Self.ID: Decodable
122321

123322
@available(SwiftStdlib 5.7, *)
124323
extension DistributedActor /*: implicitly Encodable */ where Self.ID: Encodable {
324+
325+
/// Encodes the `actor.id` as a single value into the passed `encoder`.
125326
nonisolated public func encode(to encoder: Encoder) throws {
126327
var container = encoder.singleValueContainer()
127328
try container.encode(self.id)
@@ -157,9 +358,17 @@ extension DistributedActor {
157358

158359
// ==== isRemote / isLocal -----------------------------------------------------
159360

361+
/// Verifies if the passed ``DistributedActor`` conforming type is a remote reference.
362+
/// Passing a type not conforming to ``DistributedActor`` may result in undefined behavior.
363+
///
364+
/// Official API to perform this task is `whenLocal`.
160365
@_silgen_name("swift_distributed_actor_is_remote")
161366
public func __isRemoteActor(_ actor: AnyObject) -> Bool
162367

368+
/// Verifies if the passed ``DistributedActor`` conforming type is a local reference.
369+
/// Passing a type not conforming to ``DistributedActor`` may result in undefined behavior.
370+
///
371+
/// Official API to perform this task is `whenLocal`.
163372
public func __isLocalActor(_ actor: AnyObject) -> Bool {
164373
return !__isRemoteActor(actor)
165374
}

0 commit comments

Comments
 (0)