Cannot use `?` with async?

let nonAsyncLocalVariable: [Int]? = []

func throwingAsyncFunc() async throws -> [Int] {
    []
}

let x = try await nonAsyncLocalVariable ?? throwingAsyncFunc()
wot.swift:7:44: error: 'async' call in an autoclosure that does not support concurrency
let x = try await nonAsyncLocalVariable ?? throwingAsyncFunc()
                                           ^
wot.swift:7:13: warning: no 'async' operations occur within 'await' expression
let x = try await nonAsyncLocalVariable ?? throwingAsyncFunc()
            ^
error: fatalError

…but if I do move the await like it insists:

wot2.swift:7:38: error: 'await' cannot appear to the right of a non-assignment operator
let x = try nonAsyncLocalVariable ?? await throwingAsyncFunc()
                                     ^

:cry:

It seems like ?? doesn't have an async-compatible overload? And it behaves differently to other binary operators? Presumably because it uses an @autoclosure'd parameter?

4 Likes

Yep. This is how it'd look like without autoclosed parameter:

let x = try await nonAsyncLocalVariable ?? throwingAsyncFunc()

and this with autoclosed async parameter:

let y = try await nonAsyncLocalVariable ?? (await throwingAsyncFunc())

Not ideal as you'd have to put extra await (but not extra try :thinking:).

test code
let x = try await nonAsyncLocalVariable ??? throwingAsyncFunc()
let y = try await nonAsyncLocalVariable ???? (await throwingAsyncFunc())

let nonAsyncLocalVariable: [Int]? = []
func throwingAsyncFunc() async throws -> [Int] { [] }

infix operator ???
func ??? <T>(optional: T?, defaultValue: T) -> T {
    switch optional {
        case .some(let value): value
        case .none: defaultValue
    }
}

infix operator ????
func ???? <T>(optional: T?, defaultValue: @autoclosure () async throws -> T) async rethrows -> T {
    switch optional {
        case .some(let value): value
        case .none: try await defaultValue()
    }
}

Same happens with short-circuiting bool ops:

let one = true
func two() async throws -> Bool { true }
try await one || two() // 🛑

And the same happens with other functions that take autoclosed parameters, example:

precondition(try await two()) // 🛑 'async' call in an autoclosure that does not support concurrency
preCondition{try await two()} // ✅
func preCondition(_ execute: () async throws -> Bool) {...}

Another example, same issue:

func foo() async -> Int {0}
var dict = [0:0]
dict[0, default: await foo()] // 🛑
1 Like