The Swift Concurrency Transition I Learned to Love
There was a period during the Swift Evolution Concurrency discussions that I remember very fondly.
And I was on the losing side. Or did I?
I came into the conversation questioning one of the big changes that would later become part of the “approachable concurrency” vision: the move away from the old behavior where non-isolated async functions would naturally hop off the current actor. At the time, I genuinely thought that model was one of the most brilliant parts of Swift Concurrency.
And I still think there was something beautiful in it.
Back then, one of my favorite things about Swift Concurrency was that it let developers stop worrying about threads. You didn’t have to constantly ask yourself if something needed to go “to the background”. You just wrote async code and, most of the time, things were fine. Especially in app development, where so many people come from years of GCD, Combine, Rx, and all sorts of “don’t block the main thread” patterns, that felt like a breath of fresh air.
That old promise, for some reason, mattered to me a lot.
You can even see that framing in some of my older writing. In Limit Swift Concurrency’s cooperative pool I wrote that “you should not worry about threads at all”. I still love that sentence. It captures something important about why Swift Concurrency felt refreshing in the first place.
The conversation around the original pitch and later SE-0461 is one I remember with a lot of affection. It was one of those moments that remind you the Swift Forums can be a very special place on the internet.
You could come in with a strong opinion, push back on a proposal, and instead of getting tribal nonsense, you got thoughtful replies from people who had clearly spent a lot of time thinking about the problem from multiple angles. People like Holly Borla, John McCall, Joe Groff, Mattie, and many others were not just defending a position. They were helping build a mental model.
That is a very different thing. And not something that happens often in the social interwebs these days.
Even though I was not sure about the change, it was genuinely enriching to see what the experts were seeing that I was not. That is one of the best things that can happen in a technical discussion. Not “winning”. Not getting your preferred syntax. Just having your picture of the problem expanded.
And I think that is worth saying explicitly, even now, years later, because we don’t always celebrate that enough. We talk a lot about the final proposal, the final syntax, the final accepted design. But the process of getting there matters too. Sometimes a language community proves its worth not when everybody agrees, but when disagreement is handled with care and clarity.
Why I Pushed Back
My discomfort was not really about syntax. It was about the original emotional promise of Swift Concurrency.
I liked that the model encouraged people to stop obsessing over threads. I liked being able to tell developers, especially app developers, to stop trying to manually dispatch everything around. I liked the fact that async code did not automatically mean “you might now be blocking the UI unless you remember to escape somewhere else first”.
That felt like progress.
At the time, I said this in the pitch thread:
“The idea of not blocking the main thread is pervasive in app developers.”
And also this:
“the current behaviour unloads a lot of burden once you embrace swift concurrency.”
And to be honest, I still feel a bit of grief around losing some of that simplicity. I am still annoyed that “be careful what you run on the main actor” is now a thing we have to teach more deliberately. I still think there was something elegant in a model where the default behavior helped developers avoid that whole category of thought.
That part still bothers me a little.
What Changed My Mind
What convinced me was not that my concern was silly. It was that the other side of the tradeoff was bigger than I had fully appreciated.
The key argument, as I came to understand it, was that the old behavior made one particular thing feel easy while hiding a much more serious cost, it pushed ordinary async code into situations where data-race safety became harder to reason about, especially once Swift 6’s stricter checking started showing what was actually going on.
Holly Borla made the core of that case very clearly in the thread. The line that stayed with me was:
“the current default imposes data-race safety errors”
In other words, the old world was nice partly because it let many of us avoid seeing the edges.
And once those edges started becoming visible, the old default no longer looked like a clever simplification. It looked more like a convenience that was too often in tension with the safety model Swift was trying to build.
That did not make the transition emotionally pleasant for me. But it did make it intellectually convincing.
And Then… Life Moved On
What is funny is that now the transition has happened and we are just used to the new world.
And it is fine.
This is maybe the most humbling part of language evolution. Sometimes a change feels huge when it is being discussed, deeply consequential while it is being pitched, mildly uncomfortable while it is rolling out, and then a year later it is just… the air you breathe.
That does not mean my concerns were invalid. It just means people adapt. Documentation improves, mental models settle, teams teach the new thing. The ecosystem metabolizes the change.
The world did not end and the language did not lose its soul. Swift Concurrency is still good, and in many ways it is better.
And of course, it even made me update my lovely context inheritance table, because if Swift Concurrency changes, apparently my diagrams must suffer too.
A Transition Done Right
What I appreciate the most in hindsight is not only the technical direction, but how it was handled.
The whole “approachable concurrency” effort is a great example of something that was harder to grasp when looked at as isolated pieces. Bit by bit, any one proposal could feel subtle, fussy, or overly specific. But once the direction started to materialize as a coherent vision, you could see the shape of the work more clearly.
And that, to me, is where Holly and the rest of the team deserve enormous credit.
Because the hardest part of changes like this is often not the compiler work. It is the human side. It is helping a community reframe its intuitions. It is sequencing changes so they are survivable. It is explaining tradeoffs without talking down to people. It is accepting that some resistance is not ignorance but the result of people caring deeply about the original design goals.
That is hard work. Maybe the hardest kind.
Technical systems are difficult, of course. But sociotechnical transitions are something else entirely. You are not just changing a rule. You are changing habits, explanations, blog posts, conference talks, intuitions, code review comments, and the tiny phrases people use to teach one another.
From that point of view, I think the approachable concurrency effort has been a real success.
But Don’t Forget
Even after having changed my mind, I still think caring about the main actor matters. Just not in the old GCD way of dispatching everything to the background.
Swift Concurrency is still better than that. It moves the responsibility from the caller to the code that is actually doing the expensive work. That is a meaningful distinction.
And maybe that is another reason why my fondness for the original design never fully went away.
The original promise that attracted me to Swift Concurrency is still a good promise, to help developers think less about low-level execution trivia and more about correctness and structure.
I still want that.
If anything, I think the lesson is that this promise was not defeated, but refined. The language could not keep that simplicity in exactly the form I first fell in love with. But the goal remains the right one.
And that is also something I touched on in another old post of mine, Why you should care about the future of Memory Ownership in Swift. A lot of these changes in Swift can look like they are adding complexity when viewed in isolation. But sometimes they are really the cost of making the language’s deeper guarantees more honest and more usable for normal code.
The Real Story
So maybe the real story here is not “I was wrong” or “the experts won”.
It is that I got to witness a language community do one of the hardest things in software reasonably well: change its mind in public, with care, over time.
I still remember that period with a lot of warmth. Not because every detail was pleasant, but because it reminded me what a good technical community can look like.
You come in attached to a mental model.
You push back.
People answer seriously.
You learn what tradeoff you were underestimating.
The change lands.
Life goes on.
And if things go well, you come out of it not defeated, but enlarged.
Not so bad for the losing side.