Adopted Swift 6 complete strict concurrency with no actors, bridging a blocking C event loop to the main actor through one producer, one consumer and one ordering — after establishing that a task per event loses the ordering the interface depends on.
Work carried out: 2025
Situation. Swift 6’s complete strict concurrency checking turns data races into compile errors instead of intermittent crashes. Adopting it against a C library is where it gets difficult: the core’s event loop is a blocking call that must run off the main thread forever, and the values it hands back are pointers with no concurrency guarantees at all. The compiler cannot reason about any of it and will refuse everything until the boundary is described explicitly.
Task. Complete checking had to be enabled with no escape hatches, which meant designing the crossing from a blocking C loop to the main actor rather than annotating around it.
Action. The obvious approach is an actor per subsystem, and it was rejected on measurement rather than taste. Spawning a task per incoming event lets the runtime schedule them in any order, and the core’s event stream is ordered — a message‑changed event that overtakes the message‑created event it refers to produces an interface showing an edit to something that does not exist yet. Actor reentrancy makes this worse, not better, because an actor can suspend mid‑method and process another call. What replaced it is deliberately plain: one producer thread owning the blocking loop, one consumer, one queue between them, and a single hop onto the main actor at the end. Ordering is preserved because there is exactly one path and nothing overtakes anything. The unsafe types crossing that boundary are wrapped in types whose thread‑safety is asserted at the wrapper rather than assumed, and the assertion is documented with why it holds — the pointer is owned by one thread and copied before it is handed over.
Result. The application compiles under complete strict concurrency with no suppressions, and event ordering is a structural property rather than a hope. The cost is that the design is less parallel than it could be: everything funnels through one consumer, and if that consumer ever becomes a bottleneck the fix will require re‑deriving which events can be reordered safely, which is exactly the analysis this avoided.