That's basically the answer I was looking for.
The function annotation syntax:
@MainActor func f() -> Result { … }
(with or without async, and without @asyncHandler) is something that I regard as a great new feature. I know of use cases where I want to have public API that must be called on the main thread, and there's no way of expressing that right now. (I've been using precondition to crash if the API is not called on the main thread.)
I think of it as a new feature because it's not like we don't already have lots of ways to get code executing on the main thread. In fact, execution on the main thread is pretty much the starting point for just about everything.
The code patterns I've been talking about involve getting continuations to run on the main thread — if that word makes sense here. I mean, we've got stuff to do synchronously now, and we've got stuff to do asynchronously later, and getting the later stuff onto the main thread is ugly.
For the stuff to do asynchronously later, I don't think function declaration annotations are a good fit. For example, consider code structured like this:
func g() {
… some synchronous stuff …
Task.runDetached {
let result = await f()
… do something with result …
}
… more synchronous stuff …
}
What I really wanted was for the detached task to run on the main thread. Well, f will run on the main thread, but the rest of it won't, which is a potential cause of bugs.
So, you've offered the olive branch:
func g() {
… some synchronous stuff …
Task.runDetached { @MainActor in
let result = await f()
… do something with result …
}
… more synchronous stuff …
}
That's great! That's exactly what I was looking for. Thanks.
Just … one more thing …
Given that runDetached is going to be used a lot (in either of its forms), and that it seems pretty foundational in transitioning from existing code, and that the foundational syntax around async and await (including async let) avoids mentioning Task because it's something of an implementation detail …
I'd like to suggest that runDetached should actually be provided as syntax, rather than a static method, something like:
func g() {
… some synchronous stuff …
detach {
let result = await f()
… do something with result …
}
… more synchronous stuff …
}
Personally, I'd also like to see a specialized syntax for the main thread, perhaps something like:
func g() {
… some synchronous stuff …
attach {
let result = await f()
… do something with result …
}
… more synchronous stuff …
}
However, I can see that the logic of @MainActor would compel something more like:
func g() {
… some synchronous stuff …
detach { @MainActor in
let result = await f()
… do something with result …
}
… more synchronous stuff …
}
or:
func g() {
… some synchronous stuff …
detach(@MainActor) {
let result = await f()
… do something with result …
}
… more synchronous stuff …
}
Current Swift really punishes developers by leaving no alternative except to wrap a lot of pieces of asynchronous code in DispatchQueue.main.async. To grant us a reprieve from that ugliness, it seems highly desirable to have a really, really frictionless way of saying the same thing in async-land terms. I'm not sure that Task.runDetached { @MainActor in is as frictionless as it could be.