Here's an adaptation of your program that prints out the underlying string representation in both cases.
package main
import (
"fmt"
"reflect"
"unsafe"
)
const (
Hello string = "hello"
)
func main() {
for i := 0; i < 3; i++ {
a := "hello"
sh := (*reflect.StringHeader)(unsafe.Pointer(&a))
fmt.Println(a, " ", *sh)
}
fmt.Println()
for i := 0; i < 3; i++ {
a := Hello
sh := (*reflect.StringHeader)(unsafe.Pointer(&a))
fmt.Println(a, " ", *sh)
}
}
Here's the output:
hello {4870353 5}
hello {4870353 5}
hello {4870353 5}
hello {4870353 5}
hello {4870353 5}
hello {4870353 5}
The string header in {}
in the output shows a pointer to the character data ("hello"), and the length of the string.
You can see that the pointer to the string data is the same throughout the program: the bytes data "hello" is referenced at exactly one memory address (here 4870353), no matter the loop count, and no matter whether it's a hardcoded string or a constant.
The language spec itself makes no guarantees about such behavior, but constant string interning is such a natural optimisation that it would be surprising if go behaved in any significantly different way.