> 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?
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.