14 comments

  • tdfirth1 day ago
    I don’t think this is confusing to the vast majority of people writing Go.<p>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.<p>If you come at Go from “below” then yeah it’s a bit weird.
    • onionisafruit1 day ago
      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.<p>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.<p>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.
      • carb1 day ago
        Why have you had to avoid the heap? Performance concerns?
        • malkia1 day ago
          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&#x27;t remember the exact value) then the server gets restarted.<p>Coming (back then) from C&#x2F;C++ gamedev - I was puzzled, then I understood the mantra - it&#x27;s better for the process to die fast, instead of being pegged by GC and not answering to the client.<p>Then we started looking what made it use GC so much.<p>I guess it might be similar to Go - in the past I&#x27;ve seen some projects using a &quot;baloon&quot; - to circumvent Go&#x27;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
        • ignoramous1 day ago
          Garbage Collection.<p>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 &amp; Sümer Cip talk about it a bit:<p>- <i>Optimising performance through reducing memory allocations</i> (2018), <a href="https:&#x2F;&#x2F;archive.fosdem.org&#x2F;2018&#x2F;schedule&#x2F;event&#x2F;faster&#x2F;" rel="nofollow">https:&#x2F;&#x2F;archive.fosdem.org&#x2F;2018&#x2F;schedule&#x2F;event&#x2F;faster&#x2F;</a><p>- <i>Writing GC-Friendly [Go] code</i> (2025), <a href="https:&#x2F;&#x2F;archive.fosdem.org&#x2F;2025&#x2F;schedule&#x2F;event&#x2F;fosdem-2025-5343-go-ing-easy-on-memory-writing-gc-friendly-code&#x2F;" rel="nofollow">https:&#x2F;&#x2F;archive.fosdem.org&#x2F;2025&#x2F;schedule&#x2F;event&#x2F;fosdem-2025-5...</a><p>Speaking of GC, Go 1.26 will default to a newer one viz. <i>Green Tea</i>: <a href="https:&#x2F;&#x2F;go.dev&#x2F;blog&#x2F;greenteagc" rel="nofollow">https:&#x2F;&#x2F;go.dev&#x2F;blog&#x2F;greenteagc</a>
      • tdfirth1 day ago
        Ha! I had not intended to imply that one is better than the other, but I am glad that it made you feel good :).<p>I also came &quot;from above&quot;.
    • bostik1 day ago
      As someone who writes both Python and Go (and I&#x27;ve been using Python professionally since 2005), I remember that the scoping behaviour has changed.<p>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 <i>not</i> guaranteed to vanish or go fully out of scope, but you could not <i>rely</i> 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 &quot;catch&quot; and reuse a variable after the scope it was declared in had ended, and the runtime would ensure that the &quot;second use&quot; maintained the reference count correctly. GC did not get in the way or concurrently disappear the variable from underneath you anymore.<p>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&#x27;d expect) and the target SoC device (where they didn&#x27;t).
    • compsciphd1 day ago
      I don&#x27;t see how this is coming at go &quot;from below&quot;.<p>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.<p>but that&#x27;s only because the memory location pointed to by the pointer will be unknown (even perhaps immediately). the returning of the variable&#x27;s value itself worked fine. In fact, one can return a stack allocated struct just fine.<p>TLDR: I don&#x27;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&#x27;t, it&#x27;s a &quot;struct&quot; that wraps a pointer)
      • simiones1 day ago
        The confusion begins the moment you think Go variables get allocated on the stack, in the C sense. They don&#x27;t, semantically. Stack allocation is an optimization that the Go compiler can sometimes do for you, with no semantics associated with it.<p>The following Go code also works perfectly well, where it would obviously be UB in C:<p><pre><code> func foo() *int { i := 7 return &amp;i } func main() { x := foo() fmt.Printf(&quot;The int was: %d&quot;, *x) &#x2F;&#x2F;guaranteed to print 7 }</code></pre>
        • compsciphd1 day ago
          ok, I&#x27;d agree with you in that example a go programmer would expect it to work fine, but a C programmer would not, but that&#x27;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.
          • simiones1 day ago
            I think the writer had multiple relatively weird confusions, to be fair. It&#x27;s most likely that &quot;a little knowledge is a dangerous thing&quot;. They obviously knew something about escape analysis and Go&#x27;s ability to put variables on the stack, and they likely knew as well that Go slices are essentially (fat) pointers to arrays.<p>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&#x2F;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&#x27;t get to see, ReadFromFile, but apparently this is how the author thought through the code.
        • Is that the case? I thought that it would be a copy instead of a heap allocation.<p>Of course the compiler could inline it or do something else but semantically its a copy.
          • masklinn20 hours ago
            A copy of what? It’s returning a pointer, so i has to be on the heap[0].<p>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.<p>[0] unless Foo is inlined and the result does not escape the caller’s frame, then that can be done away with.
  • foldr7 days ago
    This seems to be a persistent source of confusion. Escape analysis is just an optimization. You don&#x27;t need to think about it to understand why your Go code behaves the way it does. Just imagine that everything is allocated on the heap and you won&#x27;t have any surprises.
    • Yokohiii1 day ago
      I am currently learning go and your comment made me sort some things out, but probably in a counterintuitive way.<p>Assuming to everything allocates on the heap, will solve this specific confusion.<p>My understanding is that C will let you crash quite fast if the stack becomes too large, go will dynamically grow the stack as needed. So it&#x27;s possible to think you&#x27;re working on the heap, but you are actually threshing the runtime with expensive stack grow calls. Go certainly tries to be smart about it with various strategies, but a rapid stack grow rate will have it&#x27;s cost.
      • foldr1 day ago
        Go won’t put large allocations on the stack even if escape analysis would permit it, so generally speaking this should only be a concern if you have very deep recursion (in which case you might have to worry about stack overflows anyway).
        • masklinn20 hours ago
          &gt; Go won’t put large allocations on the stack even if escape analysis would permit it<p>Depends what you mean by “large”. As of 1.24 Go will put slices several KB into the stack frame:<p><pre><code> make([]byte, 65536) </code></pre> Goes on the stack if it does not escape (you can see Go request a large stack frame)<p><pre><code> make([]byte, 65537) </code></pre> goes on the heap (Go calls runtime.makeslice).<p>Interestingly arrays have a different limit: they respect MaxStackVarSize, which was lowered from 10MB to 128 KB in 1.24.<p>If you use indexed slice literals gc does not even check and you can create megabyte-sized slices on the stack.
          • Yokohiii17 hours ago
            There is a option -smallframes that seems to be intended for conservative use cases. Below are the related configs and a test at what point they escape (+1).<p><pre><code> &#x2F;&#x2F; -smallframes &#x2F;&#x2F; ir.MaxStackVarSize = 64 * 1024 &#x2F;&#x2F; ir.MaxImplicitStackVarSize = 16 * 1024 a := [64 * 1024 +1]byte{} b := make([]byte, 0, 16 * 1024 +1) &#x2F;&#x2F; default &#x2F;&#x2F; MaxStackVarSize = int64(128 * 1024) &#x2F;&#x2F; MaxImplicitStackVarSize = int64(64 * 1024) c := [128 * 1024 +1]byte{} d := make([]byte, 0, 64 * 1024 +1) </code></pre> Not sure how to verify this, but the assumption you can allocate megabytes on the stack seems wrong. The output of the escape analysis for arrays is different then the make statement:<p><pre><code> test&#x2F;test.go:36:2: moved to heap: c </code></pre> Maybe an overlook because it is a bit sneaky?
        • Yokohiii1 day ago
          Escape analysis accounts for size, so it wouldn&#x27;t even permit it.<p>The initial stack size seems to be 2kb, a more on a few systems. So far I understand you can allocate a large local i.e. 8kb, that doesn&#x27;t escape and grow the stack immediately. (Of course that adds up if you have a chain of calls with smaller allocs). So recursion is certainly not the only concern.
          • foldr1 day ago
            For that to be a problem you either have to have one function that allocates an enormous number of non-escaping objects below the size limit (if the Go compiler doesn&#x27;t take the total size of all a function&#x27;s non-escaping allocations into account – I don&#x27;t know), or a very long series of nested function calls, which in practice is only likely to arise if there are recursive calls.
            • Yokohiii1 day ago
              I think we mix things up here. But be aware of my newbie knowledge.<p>I am pretty sure the escape analysis doesn&#x27;t affect the initial stack size. Escape analysis does determine where an allocation lives. So if your allocation is lower then what escape analysis considers heap and bigger then the initial stack size, the stack needs to grow.<p>What I am certain about, is that I have runtime.newstack calls accounting for +20% of my benchmark times (go testing). My code is quite shallow (3-4 calls deep) and anything of size should be on the heap (global&#x2F;preallocated) and the code has zero allocations. I don&#x27;t use goroutines either, it might me I still make a mistake or it&#x27;s the overhead from the testing benchmark. But this obviously doesn&#x27;t seem to be anything super unusual.
              • foldr1 day ago
                I don&#x27;t know about your code, but in general, goroutine stacks are designed to start small and grow. There is nothing concerning about this. A call to runtime.newstack triggered by a large stack-allocated value would generally be cheaper than the corresponding heap allocation.
                • Yokohiii1 day ago
                  I found my issue, I was creating a 256 item fixed array of a 2*uint8 struct in my code. That was enough to cause newstack calls. It now went down from varying 10% to roughly 1%. Oddly enough it didn&#x27;t change the ns&#x2F;op a bit. I guess some mix of workload related irrelevancy and inaccurate reporting or another oversight on my side.
    • bonniesimon7 days ago
      Makes sense. I need to rewire how I think about Go. I should see it how I see JS.
    • 9rx1 day ago
      <i>&gt; This seems to be a persistent source of confusion.</i><p>Why? It is the same as in C.<p><pre><code> #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; struct slice { int *data; size_t len; size_t cap; }; struct slice readLogsFromPartition() { int *data = malloc(2); data[0] = 1; data[1] = 2; return (struct slice){ data, 2, 2 }; } int main() { struct slice s = readLogsFromPartition(); for (int i = 0; i &lt; s.len; i++) { printf(&quot;%d\n&quot;, s.data[i]); } free(s.data); }</code></pre>
      • simiones1 day ago
        The point the GP was making was that the following Go snippet:<p><pre><code> func foo() { x := []int { 1 } &#x2F;&#x2F;SNIP } </code></pre> Could translate to C either as:<p><pre><code> void foo() { int* x = malloc(1 * sizeof(int)); x[0] = 1; &#x2F;&#x2F;... } </code></pre> Or as<p><pre><code> void foo() { int data[1] = {1}; int *x = data; &#x2F;&#x2F;... } </code></pre> Depending on the content of &#x2F;&#x2F;SNIP. However, some people think that the semantics can also match the semantics of the second version in C - when in fact the semantics of the Go code always match the first version, even when the actual implementation is the second version.
        • 9rx1 day ago
          The semantics are clearly defined as being the same as the C code I posted earlier. Why would one try to complicate the situation by thinking that it would somehow magically change sometimes?
          • simiones1 day ago
            Because people hear that Go supports value types and so is more efficient than Java because it can allocate on the stack*, and so they start thinking that they need to manage the stack.<p>* Of course, in reality, Java also does escape analysis to allocate on the stack, though it&#x27;s less likely to happen because of the lack of value types.
            • 9rx1 day ago
              I don&#x27;t see the difficulty here. The slice is to be thought of as value type, as demonstrated in the C version. Just like in C, you can return it from a function without the heap because it is copied.
      • foldr1 day ago
        What confuses people is<p><pre><code> int *foo(void) { int x = 99; return &amp;x; &#x2F;&#x2F; bad idea } </code></pre> vs.<p><pre><code> func foo() *int { x := 99 return &amp;x &#x2F;&#x2F; fine } </code></pre> They think that Go, like C, will allocate x on the stack, and that returning a pointer to the value will therefore be invalid.<p>(Pedants: I&#x27;m aware that the official distinction in C is between automatic and non-automatic storage.)
        • knorker1 day ago
          Yes. That&#x27;s escape analysis. But this is not what OP did.<p>What you wrote is not the same in C and Go, because GC and escape analysis. But 9rx is also correct that what OP wrote <i>is</i> the same in C and Go.<p>So OP <i>almost</i> learned about escape analysis, but their example didn&#x27;t actually do it. So double confusion on their side.
          • foldr1 day ago
            Well, my point is that escape analysis has nothing to do with it at the semantic level. So it&#x27;s actually just &#x27;because GC&#x27;. You don&#x27;t need the concept of escape analysis at all to understand the behavior of the Go example.
            • knorker19 hours ago
              Yeah. That&#x27;s what I said.
              • foldr18 hours ago
                I mean that escape analysis has nothing to do with my example either, in terms of understand the semantics of the code (so I’m disagreeing with the ‘because GC <i>and escape analysis</i>’ part of your comment).
                • knorker16 hours ago
                  Your <a href="https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=46234206">https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=46234206</a> relies on escape analysis though, right?<p>Escape analysis is the reason your `x` is on the heap. Because it escaped. Otherwise it&#x27;d be on the stack.[1]<p>Now if by &quot;semantics of the code&quot; you mean &quot;just pretend everything is on the heap, and you won&#x27;t need to think about escape analysis&quot;, then sure.<p>Now in terms of what actually happens, your code triggers escape analysis, and OP does not.<p>[1] Well, another way to say this I guess is that without escape analysis, a language would be forced to <i>never</i> use the stack.
                  • foldr16 hours ago
                    Escape analysis clearly isn’t part of the semantics of Go. For that to be the case, the language standard would have to specify exactly which values are guaranteed to be stack allocated. In reality, this depends on size thresholds which can vary from platform to platform or between different versions of the Go compiler. Is the following non-escaping array value stack allocated?<p><pre><code> func pointless() byte { var a byte[1024] a[0] = 1 return a[0] } </code></pre> That’s entirely up to the compiler, not something that’s determined by the language semantics. It could vary from platform to platform or compiler version to compiler version. So clearly you don’t need to think about the details of escape analysis to understand what your code does because in many cases you simply won’t know if your value is on the stack or not.
                    • knorker15 hours ago
                      While you are of course 100% correct, in the context of discussing escape analysis I find it odd to say that it&#x27;s essentially not &quot;real&quot;.<p>Like any optimization, it makes sense to talk about what &quot;will&quot; happen, even if a language (or a specific compiler) makes no specific promises.<p>Escape analysis enables an optimization.<p>I think I understand you to be saying that &quot;escape analysis&quot; is not why returning a pointer to a local works in Go, but it&#x27;s what allows <i>some</i> variables to be on the stack, despite the ability to return pointers to other &quot;local&quot; variables.<p>Or similar to how the compiler can allow &quot;a * 6&quot; to never use a mul instruction, but just two shifts and an add.<p>Which is probably a better way to think about it.<p>&gt; So clearly you don’t need to think about the details of escape analysis to understand what your code does<p>Right. To circle back to the context: Yeah, OP thought this was due to escape analysis, and that&#x27;s why it worked. No, it&#x27;s just a detail about why <i>other</i> code does something <i>else</i>. (but not really, because OP returned the slice by value)<p>So I suppose it&#x27;s more correct to say that we were never discussing escape analysis at all. An escape analysis post would be talking about allocation counts and memory fragmentation, not &quot;why does this work?&quot;.<p>Claude (per OPs post) led them astray.
  • jstanley1 day ago
    It&#x27;s not confusing that this works in Go. (In my opinion).<p>A straightforward reading of the code suggests that it should do what it does.<p>The confusion here is a property of C, not of Go. It&#x27;s a property of C that you need to care about the difference between the stack and the heap, it&#x27;s not a general fact about programming. I don&#x27;t think Go is doing anything confusing.
    • I like Go a lot, but I often wish we could be more explicit about where allocations are. It’s often important for writing performant code, but instead of having semantics we have to check against the stack analyzer which has poor ergonomics and may break at any time.<p>But yeah, to your point, returning a slice in a GC language is not some exotic thing.
      • onionisafruit1 day ago
        I think I would like a “stackvar” declaration that works the same as “var” except my code won’t compile if escape analysis shows it would wind up on the heap. I say that knowing I’m not a language designer and have never written a compiler. This may be an obviously bad idea to somebody experienced in either of those.<p>I commented elsewhere on this post that I rarely have to think about stacks and heaps when writing Go, so maybe this isn’t my issue to care about either.
        • Scaevolus1 day ago
          This could probably be implemented as an expensive comment-driven lint during compilation.
          • onionisafruit1 day ago
            I don’t think it can be a true linter because it depends on the compiler. But it’s not a bad idea anyway
        • Yokohiii1 day ago
          Escape analysis sends large allocation to the stack. The information is there.
      • Yokohiii1 day ago
        Can you elaborate on the stack analyzer? All I could figure out was to see runtime.morestack calls that affected the runtime, but as far I remember the caller timings did exclude the cost. Having a clearer view of stack grow rates would be really great.
        • I’m not sure what you mean? Are you asking for information about what it is or how to use it?
          • Yokohiii1 day ago
            I never heard of &quot;stack analyzer&quot; and didn&#x27;t get meaningful results for it, do you mean escape analysis?
            • Sorry, yes, I meant “escape analyzer”. I’ve been jet lagged.
              • Yokohiii1 day ago
                Ok no problem. Take a good sleep soon!
  • nasretdinov1 day ago
    If the functions get inlined (which they might if they&#x27;re small enough), then the code won&#x27;t even need to allocate on heap! That&#x27;s a kind of optimisation that&#x27;s not really possible without transparent escape analysis.
  • You can run the compiler with a flag that shows all the escapes with -gcflags “-m” and there’s also support in goland and vscode to show the escapes as inline annotations in the editor. This sort of thing IMO is one of the useful things about IDEs: showing hints from later parts of the tool chain about how things are going to turn out
  • mwsherman1 day ago
    Shameless plug, if one wishes to track down allocations in Go, an allocations explorer for VS Code: <a href="https:&#x2F;&#x2F;marketplace.visualstudio.com&#x2F;items?itemName=Clipperhouse.go-allocations-vsix" rel="nofollow">https:&#x2F;&#x2F;marketplace.visualstudio.com&#x2F;items?itemName=Clipperh...</a>
    • tgv1 day ago
      That looks nice. Going to give it a try.
  • Nope, this analysis is wrong. Decompile your code and look at what&#x27;s going on: <a href="https:&#x2F;&#x2F;godbolt.org&#x2F;z&#x2F;f1nx9ffYK" rel="nofollow">https:&#x2F;&#x2F;godbolt.org&#x2F;z&#x2F;f1nx9ffYK</a><p>The thing being returned is a slice (a fat pointer) that has pointer, length, capacity. In the code linked you&#x27;ll see the fat pointer being returned from the function as values. in C you&#x27;d get just AX (the pointer, without length and cap)<p><pre><code> command-line-arguments_readLogsFromPartition_pc122: MOVQ BX, AX &#x2F;&#x2F; slice.ptr -&gt; AX (first result register) MOVQ SI, BX &#x2F;&#x2F; slice.len -&gt; BX (second) MOVQ DX, CX &#x2F;&#x2F; slice.cap -&gt; CX (third) </code></pre> The gargabe collection is happening in the FUNCDATA&#x2F;PCDATA annotations, but I don&#x27;t really know how that works.
  • potato-peeler1 day ago
    If the variable was defined in the calling function itself, and a pointer was passed, I guess the variable will still be in the heap?
    • Yokohiii1 day ago
      Pointers escape to the heap by default.
  • metadat1 day ago
    Emojis in code comments make them unreadable. Why is this a thing?
  • knorker1 day ago
    Are you sure this is what&#x27;s happening? Looks to me like the slice object is returned by value, and the array was always on the heap. See <a href="https:&#x2F;&#x2F;go.dev&#x2F;play&#x2F;p&#x2F;Bez0BgRny7G" rel="nofollow">https:&#x2F;&#x2F;go.dev&#x2F;play&#x2F;p&#x2F;Bez0BgRny7G</a> (the address of the slice object changed, so it&#x27;s not the same object on the heap)<p>Sure, Go has escape analysis, but is that really what&#x27;s happening here?<p>Isn&#x27;t this a better example of escape analysis: <a href="https:&#x2F;&#x2F;go.dev&#x2F;play&#x2F;p&#x2F;qX4aWnnwQV2" rel="nofollow">https:&#x2F;&#x2F;go.dev&#x2F;play&#x2F;p&#x2F;qX4aWnnwQV2</a> (the object retains its address, always on the heap, in both caller and callee)
    • simiones1 day ago
      Depending on escape analysis, the array underlying the slice can get allocated on the stack as well, if it doesn&#x27;t escape the function context. Of course, in this case, because we are returning a pointer to it via the slice, that optimization isn&#x27;t applicable.
      • knorker1 day ago
        Agreed that it could in principle. But I can&#x27;t immediately get it to do so: <a href="https:&#x2F;&#x2F;go.dev&#x2F;play&#x2F;p&#x2F;9hLHattS8cf" rel="nofollow">https:&#x2F;&#x2F;go.dev&#x2F;play&#x2F;p&#x2F;9hLHattS8cf</a><p>Both arrays in this example seem to be on the heap.
        • simiones18 hours ago
          Taking the address of those variables makes them escape to heap. Even sending them to the Printf function makes them escape to heap.<p>If you want to confirm, you have to use the Go compiler directly. Take the following code:<p><pre><code> package main import ( &quot;fmt&quot; ) type LogEntry struct { s string } func readLogsFromPartition(partition int) []LogEntry { var logs []LogEntry &#x2F;&#x2F; Creating an innocent slice logs = []LogEntry{{}} logs2 := []LogEntry{{}} fmt.Printf(&quot;%v %v\n&quot;, len(logs), len(logs2)) return []LogEntry{{}} } func main() { logs := readLogsFromPartition(1) fmt.Printf(&quot;%p\n&quot;, &amp;logs[0]) } </code></pre> And compile it with<p><pre><code> $ go build -gcflags &#x27;-m&#x27; main.go # command-line-arguments .&#x2F;main.go:15:12: inlining call to fmt.Printf .&#x2F;main.go:21:12: inlining call to fmt.Printf .&#x2F;main.go:13:19: []LogEntry{...} does not escape .&#x2F;main.go:14:21: []LogEntry{...} does not escape .&#x2F;main.go:15:12: ... argument does not escape .&#x2F;main.go:15:27: len(logs) escapes to heap .&#x2F;main.go:15:38: len(logs2) escapes to heap .&#x2F;main.go:16:19: []LogEntry{...} escapes to heap .&#x2F;main.go:21:12: ... argument does not escape </code></pre> However, if you return logs2, or if you take the address, or if you pass them to Printf with %v to print them, you&#x27;ll see that they now escape.<p>An additional note: in your original code from your initial reply, everything you allocate escapes to heap as well. You can confirm in a similar way.
    • masklinn1 day ago
      That’s the one.<p>Since 1.17 it’s not impossible for escape analysis to come into play for slices but afaik that is only a consideration for slices with a statically known size under 64KiB.
    • bonniesimon1 day ago
      Interesting! This could be true. I&#x27;ll play around with this in a bit.
      • knorker1 day ago
        Yeah I think what you&#x27;re describing, returning a slice thus copying a reference to the same array (but not copying the array), then destroying the callee slice not causing the array to be freed, is just basic garbage collection logic, not escape analysis.
    • lenkite1 day ago
      Yes, this is merely the slice fat pointer being copied and returned.
  • Go is returning a copy of the slice, in the same way that C would return a copy of an int or struct if you returned it. The danger of C behaviour in this instance is that a stack allocated array decays into a pointer which points to the deallocated memory. Otherwise the behaviour is pretty similar between the languages.
    • debugnik1 day ago
      I first wrote an answer about how local variables can survive through a pointer, but deleted it because you&#x27;re right that this Go code doesn&#x27;t even address locals. It&#x27;s a regular value copy.
  • gethly1 day ago
    &gt; In C, you can&#x27;t assign a value in a local function and then return it<p>I am so glad I never taken up C. This sound like a nightmare of a DX to me.
    • kjeetgill1 day ago
      Depending on what your working on, it&#x27;s actually super nice to know very clearly what lives on the stack vs the heap for performance and compactness reasons. Basically anything that didn&#x27;t come from malloc or a function calling malloc lives on the stack and doesn&#x27;t live past the function it was allocated in.<p>And these days, if you&#x27;re bothering with C you probably care about these things. Accidentally promoting from the stack to the heap would be annoying.