[Pitch] API to run a closure off of an actor (on the concurrent executor)

I'm definitely sympathetic to the use case here. A part of me would be sad to see further departure from the 'callee decides where to run' philosophy, but I'm also sympathetic to the concerns raised by @gwendal.roue about @concurrent (proposed as @execution(concurrent)) being 'different' in some sense with regards to whose 'problem' it is to decide where something runs. Actor isolation is fundamentally about correctness and the author is absolutely best-positioned to be the arbiter there, but whether particular calls need to be moved to the concurrent pool is much more about specific execution environment, which authors likely aren't well positioned to know or care about.

My suspicion is more or less what Gwendal lays out in the linked post. Library authors won't want to make things concurrent by design because that limits downstream flexibility. But from the app developer side, I still stand by what I wrote in that thread: what I usually want to do is take a synchronous, computationally expensive API and wrap it in a concurrent async function to use as the 'blessed' entry point for approximately the entire app. I specifically don't want ad hoc use sites to have to remember to wrap in runConcurrently { ... } or similar, because approximately 'nobody' downstream should be using the synchronous entry point.

Which is to say, I think the current solution of 'pull this out into a nonisolated async function has some benefits, and I worry that introducing a lightweight way to just say 'move this off to the concurrent pool' will allow/encourage a bit more laziness in deciding when such a move is actually appropriate. Of course, if people are already substantially reaching for Task.detached { ... }.value as a first line of defense (as opposed to option 1), then maybe we do just need to make this pattern more accessible.


Other thought—this is a very short wrapper for someone to write themselves. Does it clear the 'trivially composable' bar? I think that, if SE-0461 is accepted, then yes, because the 'obvious' way to write this as:

nonisolated public func runConcurrently<E, Result>(
  _ fn: @escaping () async throws(E) -> Result
) async throws(E) -> Result {
  try await fn()
}

would surprisingly not accomplish the goal here at all.

5 Likes