bbrk24
1
Why can async let variables not be used on the RHS of boolean operators?
async let first: Int[] = someAsyncCall()
async let second: Int[] = anotherAsyncCall()
let bothEmpty = (await first).isEmpty && (await second).isEmpty
// or
let bothEmpty = await (first.isEmpty && second.isEmpty)
// or any other variation I can think of
I always get the errors
'async let' in an autoclosure that does not support concurrency
Capturing 'async let' variables is not supported
I know that && and || use @autoclosure to implement short-circuit evaluation, but the error is rather opaque; I couldn't find good information on either one on Google.
What's a good way to work around this? Do I have to use an explicit task group?
nnnnnnnn
(Nate Cook)
2
As you noted, Boolean operators are defined with the rhs parameter marked as an autoclosure, which means that the expression on the right-hand side is automatically bundled up into a closure, delaying its execution until necessary. Notably, this rhs closure property isn't marked as async ,so no await statements can appear "inside" it.
As for a workaround — you should be able to put both into an array and test them at the same time:
let bothEmpty = await [first, second].allSatisfy(\.isEmpty)
bbrk24
3
Given that, I expected this to work:
func &&(
lhs: Bool,
rhs: @autoclosure () async throws -> Bool
) async rethrows -> Bool {
if lhs {
return try await rhs
} else {
return false
}
}
While that does get rid of one of the errors, it still complains about capturing the async let variable. In fact, I couldn't find any overload of && that would definitely work. Making the second argument of type Bool would shadow the standard library operator. This:
@_disfavoredOverload
func &&(lhs: Bool, rhs: Bool) -> Bool {
return Swift.&&(lhs: lhs, rhs: rhs)
}
and similar complain about various things (apparently you can't qualify operators). The only way I could find is to wrap it in a local thunk:
let and: (Bool, Bool) -> _ = { $0 && $1 }
let bothEmpty = await and(first.isEmpty, second.isEmpty)
bbrk24
4
Here's a somewhat more readable solution:
async let first: Int[] = someAsyncCall()
async let second: Int[] = anotherAsyncCall()
let (firstIsEmpty, secondIsEmpty) = await (first.isEmpty, second.isEmpty)
let bothEmpty = firstIsEmpty && secondIsEmpty