There are two questions lurking here: what do people do in the general case for their own types, and what did they do for basic collections? The choice in (original) Go was that these did not need to be equated to one another, and built-in types could be provided to fill the niche of what people actually needed without adding generic types.
Go's slice type is written []T
. Slices can share memory with arrays and each other, but they don't have to be derived from an existing fixed-size array, and they can be dynamically resized. This is one of the examples in the Go tour; using the append
function to add more elements dynamically fills in a lot of the niche of a dynamically-sized list:
var s []int
s = append(s, 0)
s = append(s, 1)
...
These are different values, but the core of list operations are achievable. The idea is that you would write the program accordingly, and so it won't be structured the same way as a Java program doing the same thing, but you can do all the things you need to do for what Go was made for.
The other core generic collection is a map or dictionary type, which Go also built in as map[Key]Value
:
var m map[string]int
m = make(map[string]int)
...
i := m["a"]
This is an implicit hash table behind the scenes, but it's an implementation detail. There's no way to get an equivalent tree map or something like that.
For custom types, the usual actual practice was to template out different versions with changed inner types, or to cast when that couldn't happen. The idea at the language-design level was that this would be rare. It's hard to tell how much that idea was true in practice — did people not do it much because they didn't need to, or because it was hard? You certainly saw instances of it in real-world Go codebases, but not as often as generics in Java ones.
The story was simply that programmers didn't actually need generics almost all the time, and supporting them would complicate the language and implementation too much to be worthwhile, while built-in slices and maps cover most of the real-world cases. Subsequent versions of Go did add generics recently, so the story may not have worked out as well as planned.