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()
^
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?
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).
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()] // 🛑