Home > OS >  How to fix "'async' call in an autoclosure that does not support concurrency"?
How to fix "'async' call in an autoclosure that does not support concurrency"?

Time:06-15

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()
    }
  • Related