I’ll start with an embarrassing Tweet of mine.
Yes, I was playing to the crowds. I have a reputation to keep as the crufty guy complaining about Swift’s overabundance of keywords. I was quickly schooled by people far cleverer than me: the actor keyword does a lot more than synthesising a protocol conformance.
Yes, actors are reference types. Yes, actors have an unownedExecutor property.
But the compiler has to synthesise this executor, and every access to an actor’s properties or functions requires compiler-enforced async boundaries to shift execution onto its serial executor.
That’s not all. The actor has a hidden runtime representation that implements the executor protocol. It orchestrates data isolation via special CPU instructions and carefully managing internal state, and performs several optimisations to run your code faster.
I’m getting ahead of myself.
Actors are islands of serialisation in a sea of concurrency*.
This is a really fun, colourful metaphor, especially if boats are your autistic special interest. But my autistic special interest is writing, and hopefully yours is giving me money.
*they actually said the same thing about DispatchQueues way back in 2009
This article isn’t about what actors do.
This is Jacob’s Tech Tavern, so I’m going to write about the under-the-hood internals.
The C++ that cuts through the fog and tames the magic. This is my first combined Concurrency and Swift Internals piece, so I hope it serves as some kind of double money glitch.
As with all my Swift internals pieces, we’re going to start high level and slowly climb down through the compiler, to see all the machinery put in place, then jump down into the pit of the runtime and discover how the sausage is made.
Contents
This email can be too long for many email clients, so please read on my website for the best experience.
Actors at Compile-time
Let me deconstruct my embarrassing Tweet for you.
The Actor Protocol
Actors are, indeed, a reference-typed entity. They implicitly conform to the “Actor” protocol, and contain an unownedSerialExecutor. This much is correct.
Executors are the “task scheduler” of Swift Concurrency. It takes jobs and schedules them for execution on the cooperative thread pool. They behave a little like a queue, where jobs may be enqueued and scheduled. They might be serial or concurrent, but Actors always implement a serial executor. Read my course for some comprehensive theory.
Making An Actorer
We can write a dead-simple actor in a few lines of code.
The compiler does a lot of work setting up the executor, associating it with the actor, and enforcing async boundaries wherever the actor is accessed. To see this compile-time machinery, we only have to ask. Run this terminal command:
swiftc -emit-sil -O actor_example.swift > actor_sil.txt
This compiles the actor a little bit, converting it into Swift Intermediate Language.
Woah, Nelly. 300 lines of SIL.
I’m not reading all that. Unless you pay me.
Subscribe to Jacob’s Tech Tavern for free to read ludicrously in-depth articles on iOS, Swift, tech, & indie projects in your inbox every week.
Paid subscribers get much more:
Read this article, plus all my Elite Hacks, my exclusive advanced content 🌟
Get my free articles 3 weeks before anyone else 🚀
Access my full Swift Concurrency course 🧵
I’m doing a promotion for new subscribers: Buy your first month of premium by Friday, and I’ll give your second month totally free!
Keep reading with a 7-day free trial
Subscribe to Jacob’s Tech Tavern to keep reading this post and get 7 days of free access to the full post archives.