FYI class constraints were merged with AnyObject in SE-0156, and AnyObject is now the preferred spelling.
Thinking about this interleaving issue at suspension points - maybe an await
could throw or somehow indicate to the caller when interleaved partial task changed common state in order to refetch state or change their computation?
That sounds complicated. If we need to check for mutation after every partial task, we might as well assume that they always mutate. And there's no distinction between an incomplete mutation (that needs guarding) and a completed one, is there?
It may be useful to have an explicit barrier, but that is no different from synchronously wait for the async task, which should already be provided in some form.
+1. The touchEnded
example also has similar problem:
@UIActor func touchEnded(...) { ... }
@UIActor func touchEndedAsync(...) async {
touchEnded()
}
Having both versions is of little use. You have essentially only one choice in any scenario (different actor, same sync actor, etc). It's all busy work here even if we use the same name* for both sync and async functions. While touchEnded
is an event handler and probably is't called directly, ui-related functions like updateUserUI
is also a good case.
* Overloading doesn't help here. The compiler could misinterpret as calling async version, which it currently does, causing infinite-loop.
(hopped from the other thread, if you know what I mean )
The code in question:
@MainActor
func foo() async {
let a = await task1()
let b = await task2()
}
Is there a case where it wouldn't be optimized? Since we could hop directy from
Task1Actor
-> Task2Actor
instead of
Task1Actor
-> MainActor
-> Task2Actor
There will definitely be some changes in ordering, but I don't yet see where that could be a problem.
That example should be reliably optimized because thereâs no significant code between two calls with known actor-independence. We may have to impose some high-level rules about when we can reasonably assume actor-independence, or else weâll find ourselves completely blocked by theoretical actor dependencies like, say, a global function reading from actor-protected state somehow.
I don't know of a way to express it in this pitch's design so I'll modify the design a bit to demonstrate:
/// Instead of applying a global actor attribute such as `@UIActor` an actor conforms to this protocol
/// and specifies the global actor context using an associated type
protocol GlobalActor: Actor {
associatedtype ActorContext
}
This change in the design allows me to express this:
final actor class Store<Value, Action, ActorContext>: GlobalActor { ... }
Instances of this type are bound to a global actor without knowing which global actor. As far as I can tell this is not possible with the current pitch for two reasons: you cannot constrain ActorContext
to be an @globalActor
and you cannot apply an @ActorContext
to the Store
class.
With this modification, we are also able to apply constraints on the ActorContext
of a GlobalActor
. For example, you could write code that is generic over another actor, but must have the same execution context as Self
, or a concrete known execution context such as UIActorContext
. The compiler could take advantage of this to allow synchronous access to synchronous API of actors from other actors in generic code (as long as they are constraints dot share the same ActorContext
). I don't see any way to support this in the pitch as written. Here's an example:
struct SomeGenericView<A: ObservableObject>: View
where A: GlobalActor, A.ActorContext == UIActorContext
{
@ObservedObject let actor: A
// The compiler would need to know this can only be called on main / UIActorContext.
// I'm not sure how to express that...
var body: some View {
// sync access to members of the actor made available via additional constraints
}
}
I think if we were going to allow this sort of actor-generics, it would need to support actor classes as well; can you figure out a way to make that work?

I think if we were going to allow this sort of actor-generics, it would need to support actor classes as well; can you figure out a way to make that work?
The Store
in my example is an actor class
so I donât understand the question. Can you elaborate?
Another example from my library is:
final actor class TupleStore2<Store0: GlobalActor, Store1: GlobalActor>: GlobalActor
where Store0.ActorContext == Store1.ActorContext
{
typealias ActorContext = Store0.ActorContext
}
In my library TupleStore2
is actually a struct. Ideally a heap allocation could be avoided but I'm not sure how that could be expressed in terms of the current pitch.
If I understand correctly, you are trying to write generics that are generic over an actor. But it looks to me that in general youâre trying to just carry the actor as a type, which will only work for global actors.
I had an idea that didnât make it into these pitches of an âactor accessoryâ type, which could have a let actor
property that would dynamically specify the right actor. That approach seems to still allow the sort of static reasoning that you want while also being theoretically extensible to generics.
I donât understand why Store
itself is an actor in your example.

If I understand correctly, you are trying to write generics that are generic over an actor. But it looks to me that in general youâre trying to just carry the actor as a type, which will only work for global actors.
If we wanted to extend my design to support a unique queue per-instance like the default actor class
I think we could do that like this:
/// Only `Never` conforms
protocol _ActorContext {}
/// User-defined global actor context types conform to this protocol:
protocol GlobalActorContext: _ActorContext {}
// If necessary, this protocol could get special treatment allowing for existentials
// until generalized existentials are a feature
protocol Actor {
// If users ever specify an explicit ActorContext without conforming to `GlobalActor`
// they get a warning or error
associatedtype ActorContext: _ActorContext = Never
}
protocol GlobalActor: Actor {
associatedtype ActorContext: GlobalActorContext
}
With this design in hand, my Store
class would use a conditional conformance:
final actor class Store<Value, Action, Context> { ... }
extension Store: GlobalActor where Context: GlobalActorContext {}
API that needs to rely on actors sharing a serialization context would be written to constrain the context of both actors to be identical and to conform to the GlobalActorContext
protocol (and therefore only available on some instances). Any API whose implementation does not rely on that constraint would be available on all instances, even those with a unique serialization context.

I donât understand why
Store
itself is an actor in your example.
It is a class that serializes access to state. I don't think the details very relevant to this discussion.
Can you explain what an ActorContext
is thatâs different from an actor? Feels like thereâs just a lot going on here that isnât clicking for me. You just want a type that serializes work but sits parasitically on another actor?
First, i'm amazed that swift get to have actors. Congrats to the team. IMHO this singlehandly could renew people's interest for the language in a server-side environment. Now for my question:
What's the target number of actors expected to run in a given system (let's say order of magnitude per cpu) ? From my understanding, great care has been put in the proposal to separate the actor definition system, from the thing actually running the functions (the task executor if i understand correctly). However, i suppose that authors of the proposal had at least some ideas of the types of usages, and the acceptable performance tradeoff they're ready to take. For example :
- in a video game server, are we expected to spawn : one actor per "arena / map / game" ? , or one actor per "connection / player", or one actor per map item (every object in the world is its own actor) ? This could be the difference between having 10 actors per CPU, to 1000 to a million.
- in a social network mobile app : are we expected to run one actor for UI (default) and one actor for background tasks (let's say, server side communication) ? or one actor per friend ? or one actor per message (dealing with its own status, like, reply, etc). Here again, the order of magnitudes could be widely different.

Can you explain what an
ActorContext
is thatâs different from an actor? Feels like thereâs just a lot going on here that isnât clicking for me. You just want a type that serializes work but sits parasitically on another actor?
An ActorContext
is modeling what your pitch calls a "global actor" in the type system. My design would have UIActorContext
instead of @UIActor
. An "actor" would be a class instance that lives within the rules of the actor system. An "actor context" would be the serialization context in which actor instances can run.
This distinction is already latent in your design where actors that are annotated with @UIActor
live in a shared serialization context. I think it's useful to make it explicit.
I recall hearing of or reading about an actor system that had a similar notion. They called it a "vat" - a vat could have many actors in it and it provided a serialization context for all of those actors, allowing code to take advantage of the knowledge that this context is shared. I haven't been able to dig up any references on this, but have used the idea in my own code and found it very useful. This allows you to have some control over (and often reduce) the granularity of serialization boundaries.
The reason I introduced the actor context protocols is to allow Never
to be used as an ActorContext
that means âno shared context, each instance has its own queueâ. If we didnât need to do that (or if we had !=
constraints) we wouldnât need the context protocols.
The programming model is not that different from yours in terms of conceptual or syntactic weight:
@globalActor
struct MyGlobalActor { }
@MyGlobalActor
actor class MyActor {}
vs
struct MyGlobalActorContext: GlobalActorContext { }
actor class MyActor: GlobalActor {
typealias ActorContext = MyGlobalActorContext
}
Itâs slightly more verbose, but has all of the advantages that come with working within the existing language and type system. I think magic attributes make sense in some cases, but not when a protocol-based design is both possible and useful without imposing a meaningful burden on less experienced programmers (i.e. without violating the principle of progressive disclosure).

I think that sync function
Actor
is a pretty good encapsulation. That's where the intuitive guarantee lies (there will be no overlap, and each sync function will run to completion without interleaves).What if we allow only
sync
functions on actors, but they must be called asynchronously from outside of that actor? So the sync part stays the same.actor class X { func foo() { } } extension X { func bar() { foo() // ok } }
Async methods become invalid for actor classes, and calling from outside of actor-isolated closures/functions makes the function async:
func foo(x: X) async { await x.foo() }
I find this idea to be very interesting. We certainly do need to allow async
functions on actors, because you need to be able to interact with the asynchronous world. However, letting synchronous actor functions be called as async on non-self
instances lets us maintain the model's consistency without introducing lots of sync/async overloading or pushing code to async
that doesn't need it.
Doug

Instead, I'd recommend make the model be that cross-actor calls are defended by access control like normal, and a cross actor call to a sync function is implicitly async (thus requiring an
await
at the call site):
@Lantua mentioned this as well. I like the idea.

Furthermore, doing this solves a significant amount of complexity elsewhere in the proposal: accesses to cross-actor state (whether it be
let
orvar
) is gated simply by access control. Any cross-actor access would be correctly async, and synchronization in the most trivial cases allowed by the proposal would be optimized out by the compiler using the as-if rule. This keeps the programmer model simple and consistent.
You won't quite get here. There are no async
properties, so you can't asynchronously access a var
within an actor.

I am very concerned about allowing direct cross-actor to
let
properties, because we don't have the ability to support computedlet
properties. Allowing this will harm our API evolution of properties: we currently allow things to freely move from let properties to vars with public getters, but this will break that.
You can define @actorIndependent
computed properties that can be used from outside. They're restricted to not touch mutable state in the actor, but it's a reasonable evolution path if you started with a let
.

Furthermore, as you mention, reference types completely break the actor memory safety guarantees here, the entire stated purpose of this proposal. :-)
No need to be dramatic. The proposal is completely up-front about the tradeoffs being made by the design and its authors are open to discussion on the right design and tradeoffs.

You don't want cross-actor uses of this thing to have access to data your mutating within the reference type. You need something like the reference type proposal (which I'm hoping to work on) to gate this.
Any solution to the reference-type problem would have to deal with this. The actorlocal
notion mentioned in the road map is an aspect of the type; a cross-actor reference to a let
whose type is actorlocal
would be ill-formed (because, you know, it's local to that actor).
I can see why this is a problem for your ActorSendable
design, because let
access is synchronous and there's no point where you can safely do the implicit copy.
Whether the potential for trouble in the ActorSendable
design makes let
access a bad idea or not, I'm not sure: I'd like to see how more of the design shakes out.

The "let's and @actorIndependent things can be talked to synchronously" breaks the contract and muddles the water.
You've conflated the two features here to draw a fairly strong conclusion. We've talked about let
above; if you have concerns about @actorIndependent
, it would be best for you to convey those directly and also consider the use cases that @actorIndependent
fulfills.

I would recommend splitting this whole discussion of global actors out to its own sub-proposal.
I'm not opposed to this. We're trying to break things up into digestible proposals without having so many little proposals running around that folks can't keep track.

Actor isolation also needs a solution for global state like global variables and static members of classes.
This is what global actors are for, but as you noted earlier...

On global actors in the detailed design section, I don't understand the writing and what is being conveyed here.

As I mentioned above, I think that the way you are conflating access control with cross-actor references is confusing and problematic. This
@actorIndependent
attribute is another example of this. I think this whole topic needs further consideration. Flipping the behavior as mentioned above seems like it would simplify the proposal significantly, by relying on our (already overly powerful) existing access control mechanisms.
As noted above, you can't simply "flip the behavior" here. I recommend you consider how to conform an actor class to CustomStringConvertible
once you've taken away the let
behavior and @actorIndependent
.

"As a special exception described in the complementary proposal Concurrency Interoperability with Objective-C, an actor class may inherit from NSObject." --> It isn't clear to me why this is needed.
Inheriting from NSObject
is currently the only way to get conformance to NSObjectProtocol
. You also need to inherit from NSObject
(directly or indirectly) to mark a class as @objc
. I looked at other options, and accepting this NSObject
-shaped inheritance wart seems like the least bad option.

Shouldn't the closure parameter to
run
be@escaping
?
Yes, or we should add the notion of a "concurrent, non-escaping" function type to the mix. We had a thread on this somewhere, but I can't find it at the moment. Short version: we could add "concurrent" to function types and it would provide some benefits here over always falling back to "escaping", but it also complicates the type system. Tradeoffs.

"Non-actor classes can conform to the Actor protocol, and are not subject to the restrictions above. This allows existing classes to work with some Actor-specific APIs, but does not bring any of the advantages of actor classes (e.g., actor isolation) to them." Ok, out of curiosity, why is this important? I can see the utility of having an actor protocol that unifies all the actors, but I don't see why it is useful for normal classes to conform. I also don't see any harm, just curious what the utility is.
My thinking here was that one could take a class that might be hard to turn into an actor, e.g., because it's enmeshed with a non-actor class hierarchy, and conform to the Actor
protocol. We could then say that calls to async
functions on such a class would get scheduled on the actor, so you're getting the hop-to-queue behavior for async calls (== less boilerplate) for free, but not the advantages of enforced data isolation.
That said, this is all very fuzzy and I'm not at all convinced that this kind of half-actor class is going to useful in practice.

I also don't think there is any great need to have actors be able to provide non-async protocol requirements. This seems directly counter to the approach of actors. Such a need can be handled with simple struct wrappers, which seems like it would factor the language complexity better.
I had not expected this view point at all, but this explains your comments about @actorIndependent
. Not being able to allow actors to conform to existing, synchronous protocols at all seems like it would make actors hard to integrate into existing Swift programs. I mentioned CustomStringConvertible
, but Identifiable
and comes to mind as being important, and a distributed actor system would sure like to make Codable
work.
Thanks for the feedback.
Doug

We had a thread on this somewhere, but I can't find it at the moment.
You mean the ones started here?

That said, this is all very fuzzy and I'm not at all convinced that this kind of half-actor class is going to useful in practice.
+1 (as in, it doesn't sound very useful in practice).
While using sync calls seems to be a kind of pessimistic locking - async suspension points feel like an optimistic strategy. Maybe thatâs some idea for using STM in the future where an implicit transaction is started on entering an actor method from outside.
So I was figuring out how actors should be treated by the type system. I'll leave this here since it should still be useful whether or not actors are part of the type system (vs part of declaration).
Let's start with the largest systemic rule, with each closure has 3 orthogonal properties (abbreviation in parentheses):
escaping (esc) < non-escaping (-)
actor independent (I) < self actor (S), global actor (G)
sync (-) < async
We also want a few extra rules so that calling from different actor is done asynchronously:
S < I async
G < I async
S esc < I esc async
G esc < I esc async
That's 12 combinations and 18 basis relations between them (not to mention relations from transitive rules). It's a pretty big poset given that it applies to each function type, e.g., (Int) -> ()
. Nonetheless, this gives us a good sanity check for other subtyping rules since that system needs to be congruent with this one (or else we'd be missing something).
Now, we can shrink the poset by merging these combinations:
-
S async + G async + I async
->async group
,- We can convert
S async
andG async
toI async
by immediately hop onto the right actor,
- We can convert
-
S esc + S esc async + G esc async + I esc async
->esc async group
- Similar to above, we can convert them to
I esc async,
- self-actor functions can't escape, convert
S esc
toI esc async
instead.
- Similar to above, we can convert them to
-
S + G
->A
- There's no notable distinction between
S
andG
, so we can useA
for both.
- There's no notable distinction between
-
G esc
->A esc
.
Now we're left with 2 axes, 6 combinations, and 7 relations:
actor independent (I) < actor isolated (A) < async group
esc < (non-esc)
or
I esc -> A esc -> esc async group
| | |
v v v
I -> A -> async group
Interestingly, S esc
somehow got pushed onto esc async group
instead of A esc
. So this type system would lose the ability to convert
S esc
(in esc async group
) to S
(in A
).
One question is where to put functions with no actor restriction/guarantee, i.e., old closures. Since there's no actor restriction, it should be together with the actor-independent (I) category. It'd restrict a lot of actor-related code from forming closures for outside consumption, but would be safe from DispatchQueue.concurrentPerform(_:)
.
Added:
Potential naming for the system above:
@escaping -> @UIActor @escaping -> @escaping async
| | |
v v v
(n/a) -> @UIActor, @actorIsolated -> async
where @actorIsolated
is allowed on local variable only.
If we don't want global actors to be type significant (because users can add them?), we can also push G esc
and G
into esc async group
and async group
, respectively, resulting in:
@escaping -> @escaping async
| |
v v
(n/a) -> @actorIsolated -> async

If we don't want global actors to be type significant (because users can add them?), we can also push
G esc
andG
intoesc async group
andasync group
, respectively,
Ok, I ended up liking this last system a lot. If we change the defaults a bit:
@escaping -> @escaping async
| |
v v
@actorIndependent -> (actorIsolated) -> async
This would retain most of what we want without mentioning a specific actor at all:
// Inside X.foo(other: X)
// esc async
let ea0 = { self.state } // self-actor
let ea1 = { other.state } // other-actor
let ea2 = { // no actor, needs await
await self.state
await other.state
}
// concurrentPerform(_: @actorIndependent () -> ())
let a0 = array.map { state[$0] } // (actorIsolated)
let a1 = array.map { sharedState } // actor independent
let a2 = array.map { other.state[$0] } // Error
let c0 = concurrentPerform { sharedState } // actor-independent
// error, cannot convert actor-isolated to actor-independent
let c1 = concurrentPerform { state }
// error, cannot convert async to actor-independent
let c2 = concurrentPerform { other.state }
Though we need to change the signature of all concurrentPerform
-like functions, which should be rare enough.
If actorIndependent
is so rare that we don't need a special keyword at all, we can push it along the poset line to async
(as opposed to escape
in the current pitch).
This way, we can do:
// // concurrentPerform(_: () async -> ())
let d0 = concurrentPerform { sharedState } // (actorIsolated)
let d1 = concurrentPerform { state } // async
let d2 = concurrentPerform { other.state } // async
which should at least be valid (albeit defeating the purpose of concurrentPerform
).
I have a concrete example which I'd like your opinion on. Let's suppose we have a program in which we start to use actors, here's one random function which returns some internal state:
actor class UserSettings {
func getAccentColor() async -> Color
}
The function is async because it has to be. Isn't it a bit odd though? This function doesn't perform a long-running task or something inherently async like a network request. Now the code calling this function needs to await on it, and all of a sudden things get more complicated, because the whole chain of calls needs to turn async. This assumes first that chains of calls can actually be made async in a reasonable fashion, that for example there is no synchronous call up the chain that is out of the developer's control (suppose a framework/library calling the developer's code synchronously and expecting a result). Furthermore, at any level in the caller chain, it might not be expected to be able to execute other functions while awaiting, something that now becomes a possibility, and it now requires further code audits and non-trivial changes to protect code from executing in an inconsistent manner. Now scale this to an entire program. Doesn't it seem overly complicated and dangerous to push async interfaces like this? Should this function really be async? Nobody seems to be discussing this problem but it seems like a real issue to me. Do you all expect this to not be an actual problem when people start using actors?