5
votes

I noticed that when a variable is captured by a closure in Swift, the closure can actually modify the value. This seems crazy to me and an excellent way of getting horrendous bugs, specially when the same var is captured by several closures.

var capture = "Hello captured"
func g(){
    // this shouldn't be possible!
    capture = capture + "!"
}

g()
capture

On the other hand, there's the inout parameters, which allow a function or closure to modify its parameters.

What's the need for inout, even captured variables can already be modified with impunity??!!

Just trying to understand the design decisions behind this...

4

4 Answers

13
votes

Variables from an outer scope that are captured aren't parameters to the routine, hence their mutablility is inherited from context. By default actual parameters to a routine are constant (let) and hence can't be modified locally (and their value isn't returned)

Also note that your example isn't really capturing capture since it's a global variable.

var global = "Global"

func function(nonmutable:Int, var mutable:Int, inout returnable:Int) -> Void {
    // global can be modified here because it's a global (not captured!)
    global = "Global 2"

    // nomutable can't be modified
//    nonmutable = 3

    // mutable can be modified, but it's caller won't see the change
    mutable = 4

    // returnable can be modified, and it's caller sees the change
    returnable = 5
}

var nonmutable = 1
var mutable = 2
var output = 3
function(nonmutable, mutable, &output)

println("nonmutable = \(nonmutable)")
println("mutable = \(mutable)")
println("output = \(output)")

Also, as you can see, the inout parameter is passed differently so that it's obvious that on return, the value may be different.

5
votes

David's answer is totally correct, but I thought I'd give an example how capture actually works as well:

func captureMe() -> (String) -> () {

    //  v~~~ This will get 'captured' by the closure that is returned:
    var capturedString = "captured"

    return {

        // The closure that is returned will print the old value,
        // assign a new value to 'capturedString', and then 
        // print the new value as well:

        println("Old value: \(capturedString)")
        capturedString = $0
        println("New value: \(capturedString)")
    }
}

let test1 = captureMe()      // Output: Old value: captured
println(test1("altered"))    //         New value: altered

// But each new time that 'captureMe()' is called, a new instance
// of 'capturedString' is created with the same initial value:

let test2 = captureMe()               // Output: Old value: captured
println(test2("altered again..."))    //         New value: altered again...

// Old value will always start out as "captured" for every 
// new function that captureMe() returns. 

The upshot of that is that you don't have to worry about the closure altering the captured value - yes, it can alter it, but only for that particular instance of the returned closure. All other instances of the returned closure will get their own, independent copy of the captured value that they, and only they, can alter.

1
votes

Here are a couple of use cases for closures capturing variables outside their local context, that may help see why this feature is useful:

Suppose you want to filter duplicates out of an array. There’s a filter function that takes a filtering predicate and returns a new array of only entries matching that predicate. But how to pass the state of which entries have already been seen and are thus duplicates? You’d need the predicate to keep state between calls – and you can do this by having the predicate capture a variable that holds that state:

func removeDupes<T: Hashable>(source: [T]) -> [T] {
    // “seen” is a dictionary used to track duplicates
    var seen: [T:Bool] = [:]
    return source.filter { // brace marks the start of a closure expression
        // the closure captures the dictionary and updates it
        seen.updateValue(true, forKey: $0) == nil
    }
}

// prints [1,2,3,4]
removeDupes([1,2,3,1,1,2,4])

It’s true that you could replicate this functionality with a filter function that also took an inout argument – but it would be hard to write something so generic yet flexible as the possibilities with closures. (you could do this kind of filter with reduce instead of filter, since reduce passes state from call to call – but the filter version is probably clearer)

There is a GeneratorOf struct in the standard library that makes it very easy to whip up sequence generators of various kinds. You initialize it with a closure, and that closure can capture variables to use for the state of the generator.

Suppose you want a generator that serves up a random ascending sequence of m numbers from a range 0 to n. Here’s how to do that with GeneratorOf:

import Darwin

func randomGeneratorOf(#n: Int, #from: Int) -> GeneratorOf<Int> {

    // state variable to capture in the closure
    var select = UInt32(n)
    var remaining = UInt32(from)
    var i = 0

    return GeneratorOf {
        while i < from {
            if arc4random_uniform(remaining) < select {
                --select
                --remaining
                return i++
            }
            else {
                --remaining
                ++i
            }
        }
        // returning nil marks the end of the sequence
        return nil
    }
}

var g = randomGeneratorOf(n: 5, from: 20)
// prints 5 random numbers in 0..<20
println(",".join(map(g,toString)))

Again, it’s possible to do this kind of thing without closures – in languages without them, you’d probably have a generator protocol/interface and create an object that held state and had a method that served up values. But closure expressions allow a flexible way to do this with minimal boiler plate.

1
votes

A closure being able to modify the captured variable in the outer scope is pretty common across languages. This is the default behavior in C#, JavaScript, Perl, PHP, Ruby, Common Lisp, Scheme, Smalltalk, and many others. This is also the behavior in Objective-C if the outer variable is __block, in Python 3 if the outer variable is nonlocal, in C++ if the outer variable is captured with &