14

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.

0

2 Answers 2

11

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"
Sign up to request clarification or add additional context in comments.

1 Comment

What if the closure is part of a framework and therefore we cannot change its arguments to accommodate an inout variable?
6

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

2 Comments

Why shadow copy? Is it shallow copy ?
"Shadow" in the sense of being hidden.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.