Swift array `filter`, `map`, etc. (higher-order functions) when the closure needs to be `async`
16:48 25 Mar 2025

Now that I'm living completely in a Swift 6 async/await world, I've suddenly hit a snag. When writing code of this sort (never mind what it does, just look at the form of the thing):

let result = services.currentPlaylist.list.filter {
    services.download.isDownloaded(song: $0)
}

I'm brought up short by the compiler, which says:

Call to actor-isolated instance method isDownloaded(song:) in a synchronous main actor-isolated context

Well, the compiler is right; services.download is, in fact, an actor. So now what? I can't say await here:

let result = services.currentPlaylist.list.filter {
    await services.download.isDownloaded(song: $0)
}

That just nets me a different error:

Cannot pass function of type (SubsonicSong) async -> Bool to parameter expecting synchronous function type

What am I supposed to do here? I can't find an async/await version of filter, except on an AsyncSequence. But services.currentPlaylist.list is not an AsyncSequence; it's an array. And even worse, I cannot find any easy way convert an array to an AsyncSequence, or an AsyncSequence to an array.

Of course I could just solve this by dropping the use of filter altogether and doing this the "stupid" way, i.e. by looping through the original array with for. At one point I had this:

var songs = services.currentPlaylist.list
for index in songs.indices.reversed() {
    if await services.download.isDownloaded(song: songs[index]) {
        songs.remove(at: index)
    }
}

But that's so ugly...

arrays swift async-await higher-order-functions