152

I noticed when writing an assert in Swift that the first value is typed as

@autoclosure() -> Bool

with an overloaded method to return a generic T value, to test existence via the LogicValue protocol.

However sticking strictly to the question at hand. It appears to want an @autoclosure that returns a Bool.

Writing an actual closure that takes no parameters and returns a Bool does not work, it wants me to call the closure to make it compile, like so:

assert({() -> Bool in return false}(), "No user has been set", file: __FILE__, line: __LINE__)

However simply passing a Bool works:

assert(false, "No user has been set", file: __FILE__, line: __LINE__)

So what is going on? What is @autoclosure?

Edit: @auto_closure was renamed @autoclosure

Joel Fischer
  • 6,521
  • 5
  • 35
  • 46

6 Answers6

277

Consider a function that takes one argument, a simple closure that takes no argument:

func f(pred: () -> Bool) {
    if pred() {
        print("It's true")
    }
}

To call this function, we have to pass in a closure

f(pred: {2 > 1})
// "It's true"

If we omit the braces, we are passing in an expression and that's an error:

f(pred: 2 > 1)
// error: '>' produces 'Bool', not the expected contextual result type '() -> Bool'

@autoclosure creates an automatic closure around the expression. So when the caller writes an expression like 2 > 1, it's automatically wrapped into a closure to become {2 > 1} before it is passed to f. So if we apply this to the function f:

func f(pred: @autoclosure () -> Bool) {
    if pred() {
        print("It's true")
    }
}

f(pred: 2 > 1)
// It's true

So it works with just an expression without the need to wrap it in a closure.

Warif Akhand Rishi
  • 23,920
  • 8
  • 80
  • 107
eddie_c
  • 3,403
  • 1
  • 16
  • 6
  • 2
    Actually the last one, doesn't work. It should be `f({2 >1}())` – Rui Peres Jun 09 '14 at 22:09
  • @JoelFischer I am seeing the same thing as @JackyBoy. Calling `f(2 > 1)` works. Calling `f({2 > 1})` fails with `error: function produces expected type 'Bool'; did you mean to call it with '()'?`. I tested it in a playground and with the Swift REPL. – Ole Begemann Jun 15 '14 at 09:53
  • I somehow read the second to last answer as the last answer, I'll have to double check, but it would make sense if it failed, as you are basically putting a closure inside a closure, from what i understand. – Joel Fischer Jun 15 '14 at 11:34
  • 3
    there is a blog post about the reason they did that https://developer.apple.com/swift/blog/?id=4 – TedMeftah Jul 19 '14 at 16:20
  • Note that as of beta 5 `@auto_closure` has been renamed to `@autoclosure`. – Patrick Pijnappel Aug 11 '14 at 04:20
  • Two things should also be noted: (1) an `@autoclosure` doesn't accept any arguments; (2) the syntax is convenient for passing function calls, where the body of the function is written separately to the closure – sketchyTech Sep 16 '14 at 11:39
  • 5
    Great explanation. Note also that in Swift 1.2 'autoclosure' is now an attribute of the parameter declaration, so it's `func f(@autoclosure pred: () -> Bool)` – Masa Feb 12 '15 at 22:09
  • As of Swift 3, `@autoclosure` (as well as `@noescape`) is now an attribute of the parameter **type** rather than the parameter name: `func f(pred: @autoclosure () -> Bool)`. See [SE-0049](https://github.com/apple/swift-evolution/blob/master/proposals/0049-noescape-autoclosure-type-attrs.md). – Daniel Rinser Sep 08 '16 at 06:42
31

Here's a practical example — my print override (this is Swift 3):

func print(_ item: @autoclosure () -> Any, separator: String = " ", terminator: String = "\n") {
    #if DEBUG
    Swift.print(item(), separator:separator, terminator: terminator)
    #endif
}

When you say print(myExpensiveFunction()), my print override overshadows Swift's print and is called. myExpensiveFunction() is thus wrapped in a closure and not evaluated. If we're in Release mode, it will never be evaluated, because item() won't be called. Thus we have a version of print that doesn't evaluate its arguments in Release mode.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    I am late to the party, but what is the impact of evaluating `myExpensiveFunction()`?. If instead of using autoclosure you pass the function to print like `print(myExpensiveFunction)`, what would be the impact? Thanks. – crom87 Jun 22 '19 at 11:56
11

Description of auto_closure from the docs:

You can apply the auto_closure attribute to a function type that has a parameter type of () and that returns the type of an expression (see Type Attributes). An autoclosure function captures an implicit closure over the specified expression, instead of the expression itself. The following example uses the auto_closure attribute in defining a very simple assert function:

And here's the example apple uses along with it.

func simpleAssert(condition: @auto_closure () -> Bool, message: String) {
    if !condition() {
        println(message)
    }
}
let testNumber = 5
simpleAssert(testNumber % 2 == 0, "testNumber isn't an even number.")

Basically what it means is you pass a boolean expression as that first argument instead of a closure and it automatically creates a closure out of it for you. That's why you can pass false into the method because it is a boolean expression, but can't pass a closure.

Connor Pearson
  • 63,902
  • 28
  • 145
  • 142
  • 15
    Note that you don't actually need to use `@auto_closure` here. The code works fine without it: `func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }`. Use `@auto_closure` when you need to evaluate an argument repeatedly (e.g., if you were implementing a `while`-like function) or you need to delay evaluation of an argument (e.g., if you were implementing short-circuiting `&&`). – nathan Jun 08 '14 at 03:59
  • 1
    @nathan Hi, nathan. Could you please cite me a sample regarding the use of `autoclosure` with a `while`-like function? I don't seem to figure that out. Thanks very much in advance. – Unheilig May 03 '15 at 04:00
  • @connor You might want to update your answer for Swift 3. – jarora Dec 03 '16 at 11:21
4

This shows a useful case of @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

Now, the conditional expression passed as the first parameter to until will be automatically wrapped up into a closure expression and can be called each time around the loop

func until<L: LogicValue>(pred: @auto_closure ()->L, block: ()->()) {
    while !pred() {
        block()
    }
}

// doSomething until condition becomes true
until(condition) {
    doSomething()
}
onmyway133
  • 45,645
  • 31
  • 257
  • 263
2

It's just a way to get rid of the curly braces in a closure call, simple example:

    let nonAutoClosure = { (arg1: () -> Bool) -> Void in }
    let non = nonAutoClosure( { 2 > 1} )

    let autoClosure = { (arg1: @autoclosure () -> Bool) -> Void in }
    var auto = autoClosure( 2 > 1 ) // notice curly braces omitted
Bobby
  • 6,115
  • 4
  • 35
  • 36
0

@autoclosure

@autoclosure converts(wraps) expression inside function parameter in a closure[About]

Pros:

  • easy to read assert(2 == 5, "failed")
  • curly braces are not used

Cons

  • hard to read. When you pass a function inside @autoclosure it is not clear that this function will be deferred(because it is closure inside). fooWithAutoClosure(a: foo0()) - foo0() will be called not immediately as we expect reading this line

Overusing autoclosures can make your code hard to understand. The context and function name should make it clear that evaluation is being deferred.

Official doc

  • @autoclosure doesn't take any parameters
    func foo(p: @autoclosure () -> Void)
    
  • @autoclosure accept any function with only appropriate returned type

More examples

//functions block
func foo0() -> String {
    return "foo0"
}

func foo1(i1: Int) -> String {
    return "foo1 " + String(i1)
}

func foo2(i1: Int, i2: Int) -> String {
    return "foo2 " + String(i1 + i2)
}
//closures block
func fooWithClosure0(p: () -> String) -> String {
    return "fooWithClosure0 " + p()
}

func fooWithClosure1(p: (Int) -> String) -> String {
    return "fooWithClosure1 " + p(1)
}

func fooWithClosure2(p: (Int, Int) -> String) -> String {
    return "fooWithClosure2 " + p(1, 2)
}
//@autoclosure
func fooWithAutoClosure(a: @autoclosure () -> String) -> String {
    return "fooWithAutoClosure " + a()
}
//test closures
func testClosures() {
    XCTAssertEqual("fooWithClosure0 foo0", fooWithClosure0(p: foo0))
    XCTAssertEqual("fooWithClosure1 foo1 1", fooWithClosure1(p: foo1))
    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: foo2))
    
    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: { (i1, i2) -> String in
        return "fooWithClosure2 " + "foo2 " + String(i1 + i2)
    }))
}
//test @autoclosure
func testAutoClosures() {
    XCTAssertEqual("fooWithAutoClosure HelloWorld", fooWithAutoClosure(a: "HelloWorld")) //"HelloWorld" is String as returned value of @autoclosure
    
    XCTAssertEqual("fooWithAutoClosure foo0", fooWithAutoClosure(a: foo0()))
    XCTAssertEqual("fooWithAutoClosure foo1 1", fooWithAutoClosure(a: foo1(i1: 1)))
    XCTAssertEqual("fooWithAutoClosure foo2 3", fooWithAutoClosure(a: foo2(i1: 1, i2: 2)))
}
yoAlex5
  • 29,217
  • 8
  • 193
  • 205