Let's find out if Golang is also subject to the same type erasure as Java or retains type information like Microsoft .NET. Please consider the following program:
package main
import (
"fmt"
"runtime"
)
type List[T any] []T
func (a *List[T]) add(val T) {
*a = append(*a, val)
}
func printLen[T any](list List[T]) {
fmt.Println(len(list))
}
func main() {
var ints List[int]
ints.add(1)
ints.add(2)
ints.add(3)
var strs List[string]
strs.add("Hello")
strs.add("world")
printLen(ints)
printLen(strs)
runtime.Breakpoint()
}
Just like the Java and .NET examples, the above program defines two variables using a very hacky, generic List
:
ints
: a list ofint
valuesstrs
: a list ofstring
values
What do ints
and strs
look like at runtime? Follow the instructions below to find out:
-
Launch the container:
docker run -it --rm go-generics-the-hard-way
-
Load the above program into the Golang debugger:
dlv debug ./05-internals/golang/main.go
-
Continue until the predefined breakpoint is hit:
continue
3 2 > main.main() ./05-internals/golang/main.go:48 (PC: 0x495c0d) 43: 44: printLen(ints) 45: printLen(strs) 46: 47: runtime.Breakpoint() => 48: }
-
Now that they are loaded into memory, print information about the
ints
andstrs
variables:locals
ints = main.List[int] len: 3, cap: 4, [...] strs = main.List[string] len: 2, cap: 2, [...]
In addition to being defined at compile-time as
List[int]
andList[string]
, the variablesints
andstrs
maintain their full type information at runtime asList[int]
andList[string]
. -
More than that though, Go does not retain information about the generic "template" at runtime. We can verify this by looking at what types exist using the debugger:
types main\.List
*main.List[go.shape.int_0] *main.List[go.shape.string_0] *main.List[int] *main.List[string] main.List[go.shape.int_0] main.List[go.shape.string_0] main.List[int] main.List[string]
Each of those types are generated by the compiler, whereever
List[T]
was instantiated. The types withgo.shape
are arguments for generic functions, which we will see in the next example. -
Print the functions:
funcs go\.shape
main.(*List[go.shape.int_0]).add main.(*List[go.shape.string_0]).add main.printLen[go.shape.int_0] main.printLen[go.shape.string_0]
Once again, only the generic types that were instantiated are maintained. And again, the
go.shape
types are arguments for generic functions. -
Type
quit
to exit the debugger. -
Type
exit
to stop and remove the container.
In other words, generics in Go do retain their type information at runtime, and in fact Go does not know about the generic "template" at runtime -- only how it was instantiated.
However, just because .NET and Go retain that type information at runtime, does it mean they know how to use it to prevent incompatible values from being added to lists? What about Java? Find out in the next section!
Next: Runtime type safety