rob
rob

Reputation: 4109

Swift closure capture and inout variables

Consider the following code:

func foo(inout success: Bool) -> (()->()) {
    return { _ in
        success = true
        print (success)
    }
}

var success = false
let closure = foo(&success)

closure()          //prints "true"
print(success)     //prints "false"

The closure appears to be creating a copy of success and does not change the original. Why is this taking place? I had assumed that the closure would point to the original because we are passing an inout variable.

Upvotes: 13

Views: 8242

Answers (2)

jscs
jscs

Reputation: 64002

This seems to be covered by Swift Evolution proposal 0035, and is considered a bug.

The document there refers to the inout parameter to the function as "a shadow copy that is written back to the argument when the callee returns". This seems to mean that there is, in essence, a temporary variable named success in the executing context of foo(). The value of that temp is then put into the outer success only when foo() returns.

Since in your foo(), the closure has not run when foo() returns, the value of the "shadow copy" has not changed. The outer success keeps its value.

The important part is that the closure has captured the shadow copy, not the outer success as you expect. So when the closure finally runs, that variable's value does change, but it no longer has any connection to the original outer success.

The proposal uses this snippet to demonstrate:

func captureAndEscape(inout x: Int) -> () -> Void {
  let closure = { x += 1 }
  closure()
  return closure
}

var x = 22
let closure = captureAndEscape(&x)
print(x) // => 23
closure()
print("still \(x)") // => still 23

Upvotes: 6

Rob
Rob

Reputation: 437532

It makes sense that this wouldn't update your success variable because your inout parameter is a parameter of foo, not of the closure itself. You get the desired behavior if you make the inout parameter a parameter of the closure:

var success = false
let closure = { (inout flag: Bool) -> () in
    flag = true
    print(flag)
}

closure(&success)  //prints "true"
print(success)     //prints "true"

This pattern also works with the function, too, as long as you keep the inout parameter a parameter of the closure:

func foo() -> ((inout Bool)->()) {
    return { flag in
        flag = true
        print (flag)
    }
}

var success = false
let closure = foo()

closure(&success)  //prints "true"
print(success)     //prints "true"

You also get the desired behavior if you use a reference type:

class BooleanClass: CustomStringConvertible {
    var value: Bool

    init(value: Bool) {
        self.value = value
    }

    var description: String { return "\(value)" }
}

func foo(flag: BooleanClass) -> (()->()) {
    return {
        flag.value = true
        print (flag)
    }
}

let success = BooleanClass(value: false)
let closure = foo(success)

closure()          //prints "true"
print(success)     //prints "true"

Upvotes: 10

Related Questions