That's a fantastic article, that actually explains very well that even the apparently simple and most contrived bit of work hides actual essential complexity, that the language, having adopted a sophisticated and safe concurrency model, properly accounts for.
I think that's not the right takeaway from the article. The complexity is real, and it's there, but it's not swept under the rug.
Another recent thread on string subscripting shows a similar pattern: something seems simple, but it's actually complex, and the language correctly accounts for the complexity.
Incidentally, the article shows one key confusing pain point with Swift right now, that has to deal with defaults (rather than actual structural problems with the strict concurrency checking model).
The error
Non-sendable type 'DataModel' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
is saying that that an instance of DataModel
, which is not Sendable
, is "crossing actor boundary" (as mentioned in the article, the implicitly
is caused by a bug, already addressed).
But it's not clear which boundary is crossing. The error mentions that there is a call to nonisolated function
, and this is the key piece that one needs to understand: the closure passed to .task
runs on the MainActor
(being a method of a SwiftUI view), but loadModel
function is not isolated to the MainActor
actor, so everything going in and out must be Sendable
(including, counterintuitively but correctly, the store
instance).
The problem is the fact that a call a nonisolated async
function is causing an isolation switch, which wouldn't be the case if the function was not async
(in fact, there would be no error if the loadModel
was synchronous).
None of this would be a problem if the loadModel
had the same isolation of the closure passed to .task
: this seems to be a more sensible "default" and, as the article mentions, it's being addressed. I'm not 100% sure about it, but my impression is that most of the confusion about strict concurrency checking in Swift would not have been there if nonisolated async
functions inherited already the isolation, like sync functions do.