| ▲ | tdfirth 4 days ago |
| I don’t think this is confusing to the vast majority of people writing Go. In my experience, the average programmer isn’t even aware of the stack vs heap distinction these days. If you learned to write code in something like Python then coming at Go from “above” this will just work the way you expect. If you come at Go from “below” then yeah it’s a bit weird. |
|
| ▲ | onionisafruit 4 days ago | parent | next [-] |
| Go has been my primary language for a few years now, and I’ve had to do extra work to make sure I’m avoiding the heap maybe five times. Stack and heap aren’t on my mind most of the time when designing and writing Go, even though I have a pretty good understanding of how it works. The same applies to the garbage collector. It just doesn’t matter most of the time. That said, when it matters it matters a lot. In those times I wish it was more visible in Go code, but I would want it to not get in the way the rest of the time. But I’m ok with the status quo of hunting down my notes on escape analysis every few months and taking a few minutes to get reacquainted. Side note: I love how you used “from above” and “from below”. It makes me feel angelic as somebody who came from above; even if Java and Ruby hardly seemed like heaven. |
| |
| ▲ | carb 4 days ago | parent | next [-] | | Why have you had to avoid the heap? Performance concerns? | | |
| ▲ | malkia 4 days ago | parent | next [-] | | For me, avoiding heap, or rather avoiding gc came when I was working (at work) on backend and web server using Java, and there was default rule for our code that if gc takes more than 1% (I don't remember the exact value) then the server gets restarted. Coming (back then) from C/C++ gamedev - I was puzzled, then I understood the mantra - it's better for the process to die fast, instead of being pegged by GC and not answering to the client. Then we started looking what made it use GC so much. I guess it might be similar to Go - in the past I've seen some projects using a "baloon" - to circumvent Go's GC heuristic - e.g. if you blow this dummy baloon that takes half of your memory GC might not kick so much... Something like this... Then again obviously bad solution long term | |
| ▲ | ignoramous 4 days ago | parent | prev [-] | | Garbage Collection. The content of the stack is (always?) known at compile time; it can also be thrown away wholesale when the function is done, making allocations on the stack relatively cheaper. These FOSDEM talks by Bryan Boreham & Sümer Cip talk about it a bit: - Optimising performance through reducing memory allocations (2018), https://archive.fosdem.org/2018/schedule/event/faster/ - Writing GC-Friendly [Go] code (2025), https://archive.fosdem.org/2025/schedule/event/fosdem-2025-5... Speaking of GC, Go 1.26 will default to a newer one viz. Green Tea: https://go.dev/blog/greenteagc |
| |
| ▲ | tdfirth 4 days ago | parent | prev [-] | | Ha! I had not intended to imply that one is better than the other, but I am glad that it made you feel good :). I also came "from above". |
|
|
| ▲ | bostik 4 days ago | parent | prev | next [-] |
| As someone who writes both Python and Go (and I've been using Python professionally since 2005), I remember that the scoping behaviour has changed. Back in Python 2.1 days, there was no guarantee that a locally scoped variable would continue to exist past the end of the method. It was not guaranteed to vanish or go fully out of scope, but you could not rely on it being available afterwards. I remember this changing from 2.3 onwards (because we relied on the behaviour at work) - from that point onwards you could reliably "catch" and reuse a variable after the scope it was declared in had ended, and the runtime would ensure that the "second use" maintained the reference count correctly. GC did not get in the way or concurrently disappear the variable from underneath you anymore. Then from 2008 onwards the same stability was extended to more complex data types. Again, I remember this from having work code give me headaches for yanking supposedly out-of-scope variable into thin air, and the only difference being a .1 version difference between the work laptop (where things worked as you'd expect) and the target SoC device (where they didn't). |
|
| ▲ | compsciphd 4 days ago | parent | prev [-] |
| I don't see how this is coming at go "from below". even in C, the concept of returning a pointer to a stack allocated variable is explicitly considered undefined behavior (not illegal, explicitly undefined by the standard, and yes that means unsafe to use). It be one thing if the the standard disallowed it. but that's only because the memory location pointed to by the pointer will be unknown (even perhaps immediately). the returning of the variable's value itself worked fine. In fact, one can return a stack allocated struct just fine. TLDR: I don't see what the difference between returning a stack allocated struct in C and a stack allocated slice in Go is to a C programmer. (my guess is that the C programmer thinks that a stack allocated slice in Go is a pointer to a slice, when it isn't, it's a "struct" that wraps a pointer) |
| |
| ▲ | simiones 4 days ago | parent [-] | | The confusion begins the moment you think Go variables get allocated on the stack, in the C sense. They don't, semantically. Stack allocation is an optimization that the Go compiler can sometimes do for you, with no semantics associated with it. The following Go code also works perfectly well, where it would obviously be UB in C: func foo() *int {
i := 7
return &i
}
func main() {
x := foo()
fmt.Printf("The int was: %d", *x) //guaranteed to print 7
}
| | |
| ▲ | samdoesnothing 4 days ago | parent | next [-] | | Is that the case? I thought that it would be a copy instead of a heap allocation. Of course the compiler could inline it or do something else but semantically its a copy. | | |
| ▲ | masklinn 3 days ago | parent [-] | | A copy of what? It’s returning a pointer, so i has to be on the heap[0]. gc could create i on the stack then copy it to the heap, but if you plug that code into godbolt you can see that it is not that dumb, it creates a heap allocation then writes the literal directly into that. [0] unless Foo is inlined and the result does not escape the caller’s frame, then that can be done away with. |
| |
| ▲ | compsciphd 4 days ago | parent | prev [-] | | ok, I'd agree with you in that example a go programmer would expect it to work fine, but a C programmer would not, but that's not the example the writer gave. I stand by my statement that the example the writer gave, C programmer would expect to work just fine. | | |
| ▲ | simiones 4 days ago | parent [-] | | I think the writer had multiple relatively weird confusions, to be fair. It's most likely that "a little knowledge is a dangerous thing". They obviously knew something about escape analysis and Go's ability to put variables on the stack, and they likely knew as well that Go slices are essentially (fat) pointers to arrays. As the author shows in their explanations, they thought that the backing array for the slice gets allocated on the stack, but then the slice (which contains/represents a pointer to the stack-allocated array) gets returned. This is a somewhat weird set of assumptions to make (especially give that the actual array is allocated in a different function that we don't get to see, ReadFromFile, but apparently this is how the author thought through the code. |
|
|
|