I've just recently re-watched a wonderful talk about progressive disclosure in Swift. The whole thing is worth your time, but there's one part in particular (about 30m-35m) that I have been thinking about a lot. That 5 minute section discusses the differences between progressive disclosure and false simplifications.
The crux of the concept is an effort to simplify a system can also suppress essential learning. This can result in an illusion of simplicity. That doesn’t mean progress isn’t happening. But, when you inevitably need to dig in deeper, only then will you encounter the true nature of the system. This is undesirable, first because real learning process hasn't actually started yet. But, also because there's the possibility that existing lessons and work might be invalided.
SE-0466 introduced the ability to control the default isolation inference. We've had just over a year to observe its use out in the real world. And I think there has been enough time and experience to justify a discussion about this feature.
I have come to believe that default isolation of MainActor is an example of false simplification.
I don't want to re-hash too much of the motivations from the proposal or the corresponding vision document. But I do think a small amount is warranted.
With a default of nonisolated, Swift has this built-in assumption of concurrency. This can present a real problem when writing single-threaded Swift code. And that's very common! It comes up when first learning Swift - a time when there is likely no intention (and possibly even no understanding) of concurrency. But it is certainly not limited to getting started. A substantial portion of real Swift code, across many domains, is single-threaded.
However, here is where I have observed the first major problem. While lots of code is single-threaded, an enormous number of real 1st- and 3rd-party APIs necessarily expose their clients to concurrency. This is not limited to lower-level systems, either. Even projects of modest complexity are unable to avoid encountering non-main thread execution when using UI frameworks exclusively. Single-threaded code is common but single-threaded libraries are a rarity.
Encountering concurrency in a form that is not compatible with a MainActor type:
a) happens for virtually all code
b) results in confusion and frustration
Now, neither of these points are unique to this mode. They can happen with a default of nonisolated too. But there is a critical difference. API design, documentation, and community-knowledge has been instilling a sense of what must be main thread-only for decades. Developers, even with limited experience, have a surprisingly good sense of what should be main-thread only.
Understanding the reverse, why something cannot be MainActor, has proven to be much harder. Interpreting the compiler diagnostics that can occur here is challenging. This is most-pronounced for users that do not understand the concurrency system. Reasoning about when a constraint is needed is more intuitive than understanding when an implicit constraint is no longer appropriate. Not to mention understanding how to remove the constraint and why it fixes the issue.
This problem, however, is not limited to understanding. Global actor isolation generally affects usage sites as well. This can permeate across a code base, potentially expanding the scope of a constraint that might not even be necessary in the first place.
This brings me to a fundamental assumption of SE-0466:
The easiest and best way to model single-threaded code is with a global actor.
Global actor isolated types do have some distinct advantages, but they also have disadvantages that demand an inverted and fairly-advanced form of thinking. I have observed, over and over, developers struggling because of their use of a MainActor default. I have also observed MainActor-isolated types that gain nothing from the isolation annotation while also paying a complexity price for having it. I think non-Sendable types, especially when paired with NonisolatedNonsendingByDefault can be, at least sometimes, a viable alternative. They are inherently single-threaded by their nature.
I do not believe this is purely academic. Non-Sendable, nonisolated types also enjoy a level simplicity and compatibility that actor-isolated types just cannot provide, even with isolated conformances. And when they are an inappropriate solution, which happens all the time, the compiler is there to provide feedback.
There's one more problem I have seen.
The vision document outlines and addresses the risks of a language dialect quite well. And I think that for the most part, they have unfolded as expected. The exception, which is critical, is readability. It is now harder to discuss Swift code. This affects documentation, code review, and snippets on this forum. It is true that there are cases where isolation isn't important or is not ambiguous. But, it regularly causes confusion. And does so particularly for situations where someone is already having difficultly. This is a second area where those with less-experience using concurrency are now subject to another, unique form of complexity.
I have also found that medium-to-large sized organizations dismiss the mode almost immediately. They tend to have highly-modular code bases, where many people review code across modules regularly. Here, the risk to readability does not outweigh any reduction in annotations.
The language dialect is, in my opinion, a very high cost to pay.
This is not a proposal. At this stage, I'm not sure it is appropriate for me to make concrete suggestions on how to proceed. I've struggled with how or even if this is something that I should bring up. I realize the implications of change here are non-trivial, possibly even impractical.
My goal here is just to get a discussion going. I'm interested to hear from others, particularly those that are using this mode with success.
Edit: I also feel it is worth clarifying that I was originally supportive of this feature. But I failed to anticipate how common types incompatible with global actors would be, combined with how much less-necessary MainActor types have become with the other features of 6.2.
