1 - Why is this a free function and not a Type method on Task? I see that in the initial pitch that it was. This seems unlike other API design in Swift.
2 - Is it just me or does the name of the function seem odd? withTaskGroup {} do what with the taskGroup? A free function that starts with 'with' seems odd to me. Does it to anyone else? Is this a side-effect of the change from being a Type method to becoming a free function? (Task.withNursery() sounds better than the solitary withTaskGroup())
(Obviously this is post-event bike shedding, so of little practical use, but I'm curious.)
It must be a "with..." style API because as you leave the scope the group is guaranteed to be destroyed and cleaned up all its inner tasks and resources.
We don't have the necessary language features to express some form of "start here; run some cleanup at end of scope" RAII style things in Swift today. This may change in the future, but today all such APIs use with... style, the same is true for withUnsafeBytes, TaskLocal's withValue etc. Specifically, it is not safe to escape the group parameter outside of this closure. We would want to make this parameter non-escapeable but this also is a feature that did not exist as the Concurrency APIs were first designed, we could perhaps make it so in the future.
The "with" enforces the structured concurrency nature of groups.
Nursery name was abandoned very quickly, during reviews, so I don't think there's much to rehash here -- you can browse all Swift Evolution review discussions if you want.
In case anyone else is interested. The rationale for moving the TaskGroup creation function out from Task to a top level function is listed as:
Move Task.withCancellationHandler to a top level function withTaskCancellationHandler which reads more logically, as it does not create a task by itself.
I don't know the actual lineage, but I like to pretend, at least, that the withXYZ style is mimicking Python's with keyword. Swift's approach isn't as elegant but it's somewhat equivalent, functionally - if you're very careful about how you implement the withXYZ function.
As a fun side effect of borrowing pattern matching, you can combine an unconditional if case borrowing pattern match with a _read coroutine to implement scope resource acquisitions in a way that's closer to what Python supports:
struct Resource: ~Copyable {}
var resource: Resource {
_read {
print("acquiring")
yield Resource()
print("releasing")
}
}
if case _borrowing r = resource {
print("using")
}
An async continuation would be useless if the language didn't allow it to escape local analysis; any code that could obey those restrictions could always be rewritten to just return normally. However, the continuation value is still scope-bound like any other with API: once withUnsafeContinuation has returned, the continuation is useless (and in fact points to deallocated memory, in the case of UnsafeContinuation). So it's not actually an exception.