Declaring a new instance of T with var

Let’s take one more look at our old friend Sum[T Numeric](...T) T:

  1. func Sum[T Numeric](args ...T) T {
  2. var sum T
  3. for i := 0; i < len(args); i++ {
  4. sum += args[i]
  5. }
  6. return sum
  7. }

The line var sum T:

  • declares a new variable sum
  • of type T
  • on the stack

It would also be possible to rewrite the function as (Golang example):

  1. func Sum[T Numeric](args ...T) T {
  2. var defaultT T
  3. var sum *T = &defaultT
  4. for i := 0; i < len(args); i++ {
  5. *sum += args[i]
  6. }
  7. return *sum
  8. }

Now there are two, declared variables:

  • defaultT
    • of type T
    • on the stack
  • sum
    • of type *T
    • on the stack

It is necessary to declare defaultT in order to take the address of the empty value of type T, because otherwise sum would point to a nil value. And yes, pointers can be on the stack. Go is able to optimize things pretty efficiently. To prove this simply take the above example and run it locally with the flags -gcflags "-m":

  1. $ docker run -it --rm go-generics-the-hard-way \
  2. go run -gcflags "-m" ./04-getting-going/01-var-t/stack
  3. # go-generics-the-hard-way/04-getting-going/01-var-t/stack
  4. 04-getting-going/01-var-t/stack/main.go:16:6: can inline Sum[go.shape.int_0]
  5. 04-getting-going/01-var-t/stack/main.go:26:17: inlining call to Sum[go.shape.int_0]
  6. 04-getting-going/01-var-t/stack/main.go:26:13: inlining call to fmt.Println
  7. 04-getting-going/01-var-t/stack/main.go:26:13: ... argument does not escape
  8. 04-getting-going/01-var-t/stack/main.go:26:17: ~R0 escapes to heap
  9. 04-getting-going/01-var-t/stack/main.go:26:17: ... argument does not escape
  10. 6

Please note that

If the function returned *T then defaultT would be moved to the heap (Golang playground):

  1. $ docker run -it --rm go-generics-the-hard-way \
  2. go run -gcflags "-m" ./04-getting-going/01-var-t/heap
  3. # go-generics-the-hard-way/04-getting-going/01-var-t/heap
  4. 04-getting-going/01-var-t/heap/main.go:16:6: can inline Sum[go.shape.int_0]
  5. 04-getting-going/01-var-t/heap/main.go:26:19: inlining call to Sum[go.shape.int_0]
  6. 04-getting-going/01-var-t/heap/main.go:26:13: inlining call to fmt.Println
  7. 04-getting-going/01-var-t/heap/main.go:26:13: ... argument does not escape
  8. 04-getting-going/01-var-t/heap/main.go:26:14: *(~R0) escapes to heap
  9. 04-getting-going/01-var-t/heap/main.go:26:19: ... argument does not escape
  10. 04-getting-going/01-var-t/heap/main.go:17:6: moved to heap: defaultT
  11. 6

In other words, whether the t in var t T ends up moving/escaping to the heap is subject to all of the same rules as normal Go code. Speaking of the heap, var t T is only one way to declare a new instance of T


Next: new(T)