I have a family of questions related to the new async/await and structured concurrency features in Swift.
If we make an async let declaration, the Xcode reports the type of the declaration as the type that the object will have after it's been awaited for. I.E.: If we
async let image = downloadImage()
then if we inspect image
, its type will be UIImage.
Question #1: Is there a way to pass an async let
declaration to a function as a parameter?
I would like to have a function that accepts an async let
declaration and transforms it somehow. The obvious way that comes to mind is to wrap the declaration into an async closure that includes an await of this declaration inside, but I wonder if there is a way to reference a type like Async<UIImage>
, Task<UIImage>
or something else so that an instance of this type could be declared as an async let
Question #2: Why does the Xcode report the type of an async let
as the type of value after an await instead of some metaphysical type representing the presence of "awaitable" context? Why isn't the type of async let
displayed as Task<UIImage>
? Why didn't the language go the ECMAScript path where an async function is supposed to return a Promise that can be both subscribed to and awaited?
Question #2: Why does the Xcode report the type of an async let as the type of value after an await instead of some metaphysical type representing the presence of "awaitable" context? Why isn't the type of async let displayed as
Task<UIImage>
? Why didn't the language go the ECMAScript path where an async function is supposed to return a Promise that can be both subscribed to and awaited?
Not exposing async let
child tasks in the type system was a deliberate decision to maintain the structured concurrency invariants, i.e. that child tasks are not allowed to escape their scope. The Swift Evolution proposal for async let
has some background:
Alternatives considered
Explicit futures
As discussed in the structured concurrency proposal, we choose not to expose futures or Tasks for child tasks in task groups, because doing so either can undermine the hierarchy of tasks, by escaping from their parent task group and being awaited on indefinitely later, or would result in there being two kinds of future, one of which dynamically asserts that it's only used within the task's scope. async let allows for future-like data flow from child tasks to parent, without the need for general-purpose futures to be exposed.
If you need an explicit task handle, you must create an unstructured task with Task { … }
or a detached task with Task.detached { … }
(in Xcode 13 beta 1 these are named async { … }
and detach { … }
, respectively). These return a Task.Handle
value, which is like a future.