Actors shouldn't exist in Swift 6
That keyword isn't needed, and there already are better ways to implement them: a hot take
One of my most controversial takes is that the actor keyword in Swift is useless, and whatever it does can be implemented in a more concise and logical way via other things already present in Swift. This is especially true with Swift 6, and doubly so with Complete Concurrency Checking.
I won't be talking about what actors are conceptually, and won't go in depth on how to use them. If you're interested in a great explanation of actors, check out Matt Massicotte's article When should you use an actor?
I will also be discussing higher-level abstractions, not the internals of Swift itself. More so the problems that I am trying to solve. I will be looking at the problem from more of a practical, instead of a theoretical, perspective.
Now, I propose the following:
- Whatever
actoris now should be replaced byclassconforming toSendable actoras a keyword should be removed- To make it possible to manually manage asynchronicity like current Sendable classes do, the current implementation of a
classconforming toSendableshould be replaced by aclassconforming to@unchecked Sendable
To make it easier to visualize, check out this code snippet:
actor Actor {}
// ↓ Replace with
class StructuredConcurrencyManagedClass: Sendable {}Replace current actor with class: Sendable
class ManuallyManagedConcurrencyClass: Sendable {}
// ↓ Replace with
class ManuallyManagedConcurrencyClass: @unchecked Sendable {}
Replace current class: Sendable with class: @unchecked Sendable
Why?
When do I use actors? When I need a reference type that doesn't mess up Swift's asynchronicity.
And then I started thinking: we already have a reference type: class.
And we also have a way to make an object "not mess up asynchronicity": Sendable.
Now try to conform a class to Sendable. It will complain about mutable state not being concurrency-safe.


Various errors caused by a Sendable class with mutable state
And at this point, you have to change the class to actor, rather unintuitively, instead of conforming the class to Sendable to ensure races don't happen, like I would expect.
These days, as far as I was able to dig up, Sendable classes are still used when you want to manage concurrency manually via locks, etc, which may be why they are distinct from actors, ignoring mostly theoretical features, such as isolation domains. This in itself is a pretty niche use case if you aren't working on a legacy codebase; moreover, we also have a way to opt an object out of that system: @unchecked Sendable, which basically tells the compiled to peace out and trust you that you'll prevent data races yourself. Why not use it here?
And with that, I arrive to the conclusion that there is no need for actor as a keyword. Everything actor does and doesn't do can be more logically accomplished by combining other features of Swift.
How did this happen?
I believe a lot of the confusion about concurrency happened because Apple initially made structured concurrency a black box that nobody understood. They tried to take their "it just works" and "the only bug-free code is the code the programmer didn't write" philosophies and apply them to something as complex as multi-threading. When edge cases started piling up, they created the actor keyword to try to not pollute their already existing systems with them.
But now that we have approachable concurrency, I'd say it's time to revisit the actor as a keyword and discuss its actual use.
For many, including me, structured concurrency and concurrency checking became less something to be understood, and more something that you fight against. Likewise, actor became nothing other than a "reference type that doesn't complain about data races."
Closing thoughts
Like I said at the beginning of the article, I'm far from an expert on Swift concurrency, and I'm looking at the topic more as a self-taught hobbyist.
Comments ()