NEW: Learn to build amazing SwiftUI apps for macOS with my new book! >>

async let bindings

Available from Swift 5.5

Paul Hudson      @twostraws

SE-0317 introduced the ability to create and await child tasks using the simple syntax async let. This is particularly useful as an alternative to task groups where you’re dealing with heterogeneous result types – i.e., if you want tasks in a group to return different kinds of data.

To demonstrate this, we could create a struct that has three different types of properties that will come from three different async functions:

struct UserData {
    let name: String
    let friends: [String]
    let highScores: [Int]

func getUser() async -> String {
    "Taylor Swift"

func getHighScores() async -> [Int] {
    [42, 23, 16, 15, 8, 4]

func getFriends() async -> [String] {
    ["Eric", "Maeve", "Otis"]

If we wanted to create a User instance from all three of those values, async let is the easiest way – it run each function concurrently, wait for all three to finish, then use them to create our object.

Here’s how it looks:

func printUserDetails() async {
    async let username = getUser()
    async let scores = getHighScores()
    async let friends = getFriends()

    let user = await UserData(name: username, friends: friends, highScores: scores)
    print("Hello, my name is \(, and I have \(user.friends.count) friends!")

Important: You can only use async let if you are already in an async context, and if you don’t explicitly await the result of an async let Swift will implicitly wait for it when exiting its scope.

When working with throwing functions, you don’t need to use try with async let – that can automatically be pushed back to where you await the result. Similarly, the await keyword is also implied, so rather than typing try await someFunction() with an async let you can just write someFunction().

To demonstrate this, we could write an async function to recursively calculate numbers in the Fibonacci sequence. This approach is hopelessly naive because without memoization we’re just repeating vast amounts of work, so to avoid causing everything to grind to a halt we’re going to limit the input range from 0 to 22:

enum NumberError: Error {
    case outOfRange

func fibonacci(of number: Int) async throws -> Int {
    if number < 0 || number > 22 {
        throw NumberError.outOfRange

    if number < 2 { return number }
    async let first = fibonacci(of: number - 2)
    async let second = fibonacci(of: number - 1)
    return try await first + second

In that code the recursive calls to fibonacci(of:) are implicitly try await fibonacci(of:), but we can leave them off and handle them directly on the following line.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Spend less time managing in-app purchase infrastructure so you can focus on building your app. RevenueCat gives everything you need to easily implement, manage, and analyze in-app purchases and subscriptions without managing servers or writing backend code.

Get Started

Sponsor Hacking with Swift and reach the world's largest Swift community!

Other changes in Swift 5.5…

Download all Swift 5.5 changes as a playground Link to Swift 5.5 changes

Browse changes in all Swift versions

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.