I have these two functions (which compile without errors):
func hasLocalChanges() -> Bool {
return false
}
func hasRemoteChanges() async -> Bool {
return await Task{ true }.value
}
Now, let's say I want to introduce a third function which will look like this:
func hasChanges() async -> Bool {
return self.hasLocalChanges() || await self.hasRemoteChanges()
}
Then this function will give me a compiler error saying:
'async' call in an autoclosure that does not support concurrency
But why is that?
I can resolve the error by swapping the operands …
func hasChanges() async -> Bool {
return await self.hasRemoteChanges() || self.hasLocalChanges()
}
…, which, again, will make the code compile without errors.
But I really want to make use of lazy evaluation and have the asynchronous function be executed last. So the only way to achieve that would be to say …
func hasChanges() async -> Bool {
if !self.hasLocalChanges() {
return await self.hasRemoteChanges()
} else {
return true
}
}
…, which seems a little cumbersome.
Can anyone explain to me why I am getting that error in the first place?
EDIT:
Thanks a lot to @aciniglio:
One way to rewrite the code is like this (assuming the two functions are also allowed to throw):
func hasChanges() async throws -> Bool {
return try await hasLocalChanges().or(await hasRemoteChanges())
}
extension Bool {
func or(_ other: @autoclosure () async throws -> Bool) async rethrows -> Bool {
return self ? true : try await other()
}
}
CodePudding user response:
It's because ||
is actually a function that wants the right hand side to look like rhs: @autoclosure () throws -> Bool
(vs. e.g rhs: @autoclosure () async throws -> Bool
)
See the source here: https://github.com/apple/swift/blob/e6cbf5483237aa593bdbafb6c7db7ebcb2d0e26a/stdlib/public/core/Bool.swift#L320
When you move the await first, it resolves into a boolean and then self.hasLocalChanges()
is a non async () throws -> Bool
Examples that compile below
func hasChanges() async -> Bool {
return or( a: await hasRemoteChanges(), b: hasLocalChanges())
}
// This is pretty much what `||` looks like today
func or(a: Bool, b: @autoclosure () throws -> Bool) -> Bool {
return a ? true : try b()
}
func hasChanges() async -> Bool {
return await or( a: hasLocalChanges(), b: await hasRemoteChanges())
}
// This is what an `async` friendly `||` could look like.
// Note that the caller would need to await the result.
func or(a: Bool, b: @autoclosure () async throws -> Bool) async -> Bool {
return a ? true : try! await b()
}