Primitive escapes to heap

After doing several escape analysis tests, I have come across something that I cannot wrap my head around. Given the ultra simple code, why does Age supposedly escape to the heap:

package main

import "fmt"

type Person2 struct {
    Age int
}

func (p *Person2) Dump() {
    fmt.Println(p.Age)
}

func main() {
    person := Person2{
        Age: 99,
    }
    person.Dump()
}

Running:

go run -gcflags="-m" ./mem2/main2.go

Returns:

mem2/main2.go:9:6: can inline (*Person2).Dump
mem2/main2.go:10:13: inlining call to fmt.Println
mem2/main2.go:17:13: inlining call to (*Person2).Dump
mem2/main2.go:17:13: inlining call to fmt.Println
mem2/main2.go:9:7: p does not escape
mem2/main2.go:10:13: ... argument does not escape
>>>  mem2/main2.go:10:15: p.Age escapes to heap  <<<
mem2/main2.go:17:13: ... argument does not escape
mem2/main2.go:17:13: p.Age escapes to heap

I have added >>>, <<< to show the line of interest. This is a “primitive” struct with only 1 primitive type, initialised and handled in such a way as to avoid the heap. So why is Age escaped to the heap. Calling Dump by value instead of reference also makes no difference to the analysis.

How do I read this, for it says it escapes and I assume that the very next line says, it does not escape?

Hello there. It’s happening because of the fmt.Println function call. Under the hood it operates over interfaces and regularly it causes a value to escape to the heap. In most cases optimizations of the memory management (specifically interfacing with system calls for output) assumes this interface is going to be passed around thus, it’s easily to store it on the heap. You can replace with direct write into stdout to see, that it won’t escape.

1 Like

Thank you. I have tested what you said and you are 100% correct.