8

I know there are several related question and moreover I can find many posts in the Internet. However, I can't understand the fact that closures can hold references. In case of a reference type, it is totally usual and very reasonable, but how about a value type, including struct and enum? See this code.

let counter: () -> Int
var count = 0
do  {
    counter = {
        count += 1
        return count
    }
}
count += 1 // 1
counter() // 2
counter() // 3

We can access the value type count through two ways. One is by using count directly and the another is through the closure counter. However, if we write

let a = 0
let b = a

, in the memory b has of course a different area with a because they are value type. And this behavior is a distinct feature of value type which is different with reference type. And then backing to the closure topic, closure has the reference to value type's variable or constant.

So, can I say the value type's feature that we can't have any references to value type is changed in case of closure's capturing values? To me, capturing references to value type is very surprising and at the same time the experience I showed above indicates that.

Could you explain this thing?

Hamish
  • 78,605
  • 19
  • 187
  • 280
Kazuya Tomita
  • 667
  • 14
  • 34
  • 3
    I'm not sure how much into the technical details you want to get, but https://stackoverflow.com/a/40979548/2976878 & https://stackoverflow.com/q/43171341/2976878 may be useful. Basically, captured values are put into reference-counted boxes on the heap, which are then stored on the function value's context object. When it comes to capturing variables holding reference types, the reference itself is put into the heap-allocated box (consider that references *themselves* are value types), so what you have in effect is a reference to a reference. – Hamish Jun 20 '17 at 09:14
  • I read your post and it is quite exciting. And I understood the content. However, what is reference-counted boxes? I read your answer you showed many times, but I haven't met the word. Could you explain? – Kazuya Tomita Jun 22 '17 at 14:04
  • Glad you found it useful! A box is just a wrapper for a given value, so with closure capture, the value of a captured variable is "boxed" on the heap in a structure with a layout of the specialised `Box` struct in my answer. Then (mostly) any future accesses to that variable (in the scope that it's defined in, as well as the closure) are just compiled as accessing the boxed value on the heap. – Hamish Jun 22 '17 at 15:54
  • 1
    Although that being said, strictly speaking in *your* example, `count` is a *global* variable, and therefore has a static lifetime, meaning that the closure doesn't *technically* capture it – it just accesses it normally (if you're interested, I go a bit into the implementation of global and static accessors [here](https://stackoverflow.com/a/43379040/2976878)). – Hamish Jun 22 '17 at 15:54
  • But I digress. Reference counting, in this case, is just way of tracking the lifetime of a block of memory on the heap, and as the name suggests, is done by keeping a count of the number of references to that memory, deallocating it when the number of references == 0. Swift uses [Automatic Reference Counting](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html) to do this. – Hamish Jun 22 '17 at 15:54
  • In your explanation, a global stored variable has the accessor `unsafeMutableAddressor`. In my case, `count` seems to be not necessarily the global stored variable(Of course I know there are few information about it). However, how about local scope variables? Is the behavior same with global stored variables? – Kazuya Tomita Jun 23 '17 at 05:01
  • Ah well I (perhaps prematurely) assumed that the example you gave was top-level code (in a main.swift file, or playground). If it is, then both `counter` and `count` are global variables (have static lifetime, are accessible from other swift files in the same module, as `internal`). Global variables aren't technically captured by closures, they're just accessed normally. – Hamish Jun 23 '17 at 09:35
  • Although that being said, I forgot that main.swift and playgrounds are a special case when it comes to globals – globals are treated more like top-level local variables, so aren't lazily initialised and don't have an `unsafeMutableAddressor` accessor (but still aren't technically captured). Therefore the Q&A I linked to isn't too relevant in this particular case (only for globals declared in other swift files). – Hamish Jun 23 '17 at 09:35
  • If however `count` & `counter` are local variables in a function, e.g `func foo() {/* your example here */}`, then `count` will be captured by the closure (using the exact behaviour that I described in my first comment). But don't let this technical difference between global and local variables affect how you think about closure capture from a high level perspective; you should always just still think in terms of variables being captured, just as Rob describes it. – Hamish Jun 23 '17 at 09:37
  • Thank you for your kind comments. All your comments are excellent for me. Let me summarize the behavior (and please correct me if wrong). When a closure captures a value type variable which is local, does the closure has the value itself? As you know, a value type variable is on the stack, and closure can capture something on the box area. In this case, what exists in the box area of the closure? Reference or value itself? I think you mentioned in case of a reference type, but I couldn't find any answer in case of a value type. – Kazuya Tomita Jun 23 '17 at 13:10
  • When a closure captures a value-typed variable, the value of that variable is put into a box (`Box` from my answer) on the heap. The reference to this box is then stored on the closure's context. The closure, as well as the enclosing scope that defined the local variable then accesses the value of the variable from this box, ensuring they're all "seeing" the same value. It's the exact same behaviour with a reference type – the reference *itself* (which is a value type!) is boxed (so we have a reference to the box which has a reference to the instance). – Hamish Jun 23 '17 at 19:24
  • Does [this image](http://i.imgur.com/xisLgEK.png) help clarify things? – Hamish Jun 23 '17 at 19:24
  • Let's say we have a local value-typed variable. Before capturing by a closure, the variable refers to the value and the value itself is also on the stack. And then after capturing, all accesses to the value is done through the value in the box on the heap. So, when the access to the variable which had the value is done, what happened? Swift automattically adjusts it? – Kazuya Tomita Jun 24 '17 at 03:35
  • And could you answer my questions on your answer of https://stackoverflow.com/questions/43374222/in-swift-why-does-assigning-to-a-static-variable-also-invoke-its-getter/43379040#43379040? – Kazuya Tomita Jun 24 '17 at 03:37
  • The box is allocated on the variable's *declaration* if Swift sees that it is to be captured by a closure ("if a closure captures" would've been more accurate than "when a closure captures"). – Hamish Jun 24 '17 at 11:04
  • Very sorry for my persisting questions. But let me ask final question of you. After the closure captures the value type variable, does the variable actually have the reference to the value itself on the box? Or the value itself? Because the box is on the heap, I am not sure of it. – Kazuya Tomita Jun 24 '17 at 11:57
  • Or the variable just refers to the value on the box? Which is right? – Kazuya Tomita Jun 24 '17 at 12:07
  • I'm not sure I understand the distinction you're making – when Swift sees that you're accessing a local variable that has been captured by a closure, it will be compiled as accessing the value in the heap-allocated box (so, dereferencing the pointer to the box; and getting the value at the given offset – or using a pointer directly to the value). But really this discussion is getting too long – if you have a further question, I would advise posting it as a new question, linking to this post, and possibly the other Q&A for context. – Hamish Jun 24 '17 at 12:34

1 Answers1

7

I think the confusion is by thinking too hard about value types vs reference types. This has very little to do with that. Let's make number be reference types:

class RefInt: CustomStringConvertible {
    let value: Int
    init(value: Int) { self.value = value }
    var description: String { return "\(value)" }
}

let counter: () -> RefInt
var count = RefInt(value: 0)
do  {
    counter = {
        count = RefInt(value: count.value + 1)
        return count
    }
}
count = RefInt(value: count.value + 1) // 1
counter() // 2
counter() // 3

Does this feel different in any way? I hope not. It's the same thing, just in references. This isn't a value/reference thing.

The point is that, as you note, the closure captures the variable. Not the value of the variable, or the value of the reference the variable points to, but the variable itself). So changes to the variable inside the closure are seen in all other places that have captured that variable (including the caller). This is discussed a bit more fully in Capturing Values.

A bit deeper if you're interested (now I'm getting into a bit of technicalities that may be beyond what you care about right now):

Closures actually have a reference to the variable, and changes they make immediately occur, including calling didSet, etc. This is not the same as inout parameters, which assign the value to their original context only when they return. You can see that this way:

let counter: () -> Int
var count = 0 {
    didSet { print("set count") }
}

do  {
    counter = {
        count += 1
        print("incremented count")
        return count
    }
}

func increaseCount(count: inout Int) {
    count += 1
    print("increased Count")
}

print("1")
count += 1 // 1
print("2")
counter() // 2
print("3")
counter() // 3

increaseCount(count: &count)

This prints:

1
set count
2
set count
incremented count
3
set count
incremented count
increased Count
set count

Note how "set count" is always before "incremented count" but is after "increased count." This drives home that closures really are referring to the same variable (not value or reference; variable) that they captured, and why we call it "capturing" for closures, as opposed to "passing" to functions. (You can also "pass" to closures of course, in which case they behave exactly like functions on those parameters.)

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thank you for great answer! However, I am still not clear. What do you mean by "the closure captures the variable". I can't imagine this well. As you know, a variable to the `class`'s instance contains the instance's memory's address and is in the stack area. On the other hand, a variable corresponding to value types is the value itself and is also in the stack area. What does the closure have when the closure captures something? You explained the variable itself, but how dose the closure have them if you give me more detail explanation? – Kazuya Tomita Jun 20 '17 at 13:32
  • See Hamish's links in his comment above for discussion of the implementation. (The captured variables are placed in a reference box on the heap.) But the important distinction is that "a variable corresponding to value types" is *not* "the value itself." It is a name that *refers* to a value and can change which value it refers to. This is very important. If it were "the value itself" then `var` would be meaningless. (`let` can be thought of this way, as just a "binding" rather than a reference, and most of these questions vanish when everything is `let`.) – Rob Napier Jun 20 '17 at 14:19
  • A variable (`var`) is a reference to a value, not the value itself. The word "reference" gets used for many things, and this can be confusing. For example, just because something is a "value type" does not mean it has "value semantics" and vice versa. For instance, an `NSDate` is a reference type, but has value semantics. It is quite easy to build a struct (value type) with reference semantics. Whether these things are on the stack or the heap is entirely an implementation detail (and Swift moves some things between stack and heap). Some types aren't on the stack *or* heap (tagged pointers). – Rob Napier Jun 20 '17 at 14:25
  • 1
    A very useful talk on the subject: https://news.realm.io/news/swift-gallagher-value-semantics/ – Rob Napier Jun 20 '17 at 14:27