Relax synchronous @autoclosure constraint

Currently, when a function has an '@autoclosure` attributed closure parameter, that closure is constrained to being synchronous.

func a(_ f: @autoclosure @escaping () async -> Int) {}
// ERROR: 'async' autoclosure parameter in a non-'async' function

This means that if you wish to express that a closure parameter may auto-close, but should also optionally accept an async closure parameter you have to overload the function:

func a(_ f: @autoclosure @escaping () -> Int) {}
func a(_ f: @escaping () async -> Int) {}

Is there any reason we shouldn't make this the default behaviour of @autoclosure. i.e. if the parameter has been 'auto-closed', assume the supplied closure is synchronous?

The only reason I can think that this would be a problem is if an asynchronous expression is evaluated at the call-site a(await getFunc()), but theoretically the complier would ultimately handle this in the same way as it handles the overloaded example above.

Hi! Note that the @autoclosure always auto-closes, even without the async. If you try to pass in a closure directly, it will give you an error:

func b(_ f: @autoclosure () -> Int) { }
let closure: () -> Int = { 5 }
b(closure) // error: Add () to forward @autoclosure parameter

Why? It would add complexity to the language, and make things ambiguous

func c(_ f: @autoclosure () -> Any) {
    print(type(of: f()))
}
let closure: () -> Int = { 5 }
c(closure) // should it print "() -> Int" or "Int" ?
           // Currently it prints "() -> Int", but without auto-closing it would print "Int" instead

The solution is simple, and you already discovered it: overload the function.

1 Like

Got it. OK, thank you. That makes a lot of sense. Clearly, I'm a bit late to the @autoclosure party :slight_smile:

1 Like