Hacker News new | past | comments | ask | show | jobs | submit login

No, it can just use a different variable each loop turn?

Go authors themselves admit it might have been a design mistake, but is unfixable without breaking compatibility.

https://golang.org/doc/faq#closures_and_goroutines

> This behavior of the language, not defining a new variable for each iteration, may have been a mistake in retrospect. It may be addressed in a later version but, for compatibility, cannot change in Go version 1.




> No, it can just use a different variable each loop turn?

How does that work? The call-frame needs to be of fixed-size, but it's rarely knowable at compile time how many iterations a loop will perform. Unless we begin heap-allocating loop variables or treat each loop iteration as a function call (both of which will kill performance, I think), I don't see how this could work?


You seem to have some fundamental misunderstanding about what costs memory. As the documentation says, the following works fine:

    for _, v := range values {
        v := v // create a new 'v'.
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }
The scope of the variable has no effect on how many values may or may not be allocated.


> You seem to have some fundamental misunderstanding about what costs memory.

Probably, hence my "How does that work?" question.

I don't doubt your example--I know it works, but I'm struggling to understand how it manages to work, since (as I understand it) the closure presumably captures the environment, which presumably means taking the address to the new `v` which presumably is the same address for each loop iteration?


https://en.wikipedia.org/wiki/Funarg_problem

Whether a loop is involved is irrelevant, it comes up any time a value referenced a closure might outlast the environment which created the closure. That can happen in loops but also if conditions or normal function calls. The value might be promoted to the heap, but in trivial cases like the above it could be copied directly on the new goroutine's stack. Or if escape analysis says the closure can't outlast the environment, it will use the same stack.


That makes sense, so most likely Go's compiler is promoting the new variable to the heap (I doubt it's smart enough to recognize that the new `v` is only used by the closure which in turn is only used by the new goroutine and thus safe to allocate `v` in the new goroutine's stack). In any case, thanks for the link, I didn't know there was a formal name for the problem.


So again: Allocations of regions of memory representing values, not variables, are what does or does not get promoted. If `values` is an array of strings, do you think the following makes a new string on the heap (that is, a new 16 byte heap allocation, internally pointing to the same variable-sized region of memory)?

   for _, v := range values {
        go func(v string) {
            fmt.Println(v)
            done <- true
        }(v)
    }
No, the 16 bytes will be copied onto the goroutine's stack.

Do you think it's smart enough to do the trivial transformation of the first into the second?


> So again: Allocations of regions of memory representing values, not variables, are what does or does not get promoted.

Right, I understand the difference between a variable and a region of memory.

> If `values` is an array of strings, do you think the following makes a new string on the heap (that is, a new 16 byte heap allocation, internally pointing to the same variable-sized region of memory)?

No.

> Do you think it's smart enough to do the trivial transformation of the first into the second?

I guess I'm surprised that the semantics for the `v := v` in a loop thing are "this will get allocated somewhere else such that it's guaranteed not to be stomped on by another loop iteration". For example:

    for _, v := range []int{2, 4, 6} {
        v := v
        mut := sync.Mutex{}
        go func() {
            mut.Lock()
            fmt.Println(v)
            mut.Unlock()
            done <- true
        }()
        go func() {
            mut.Lock()
            v = 1
            mut.Unlock()
            done <- true
        }()
    }
If it allocates `v` on the goroutines' stacks, then it won't catch the mutation, so presumably it allocates on the heap at least in that case. In whatever case, the semantics are surprising to me.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: