perhaps, but i think it's likely an artifact of top-level code, which has a number of its own discrepancies & quirks. if you replace the do block with a function, the expected concurrency errors appear (on the nightly toolchains at least). i found discussion about a similar scenario in this github issue.
There is a logical race here, but I do not think this is a bug (yet!). I believe the problem is this:
func f4(_ closure: sending @escaping () async -> Void) {
Task.detached {
// "closure" here was formed on and will be executed on the MainActor
await closure()
}
}
You are correct, the synchronous code executed within the detached task will be done on a background thread. But closure is not synchronous, and that await will move execution back to the MainActor.
Further, because the closure is not @Sendable, it can never be moved from the MainActor, where it is formed, to the background. Only @Sendable closures can cross actor boundaries like that.
Wait let me revise my response! The closure is non-isolated and async, so its body will not be MainActor-isolated. Sorry about the misunderstanding!
Yeah, it does seem like this is allowing you to sneak a non-Sendable across boundaries in a way that should not be allowed. I definitely think this is worth a bug.
(but also, @jamieQ is right, this is correctly caught in non-top-level code)