13
$\begingroup$

Go (was) famous for its lack of generics. However, the lack of generics in a statically typed language makes quite a few things harder. For example (in a pseudocode because I don't know Go):

let x: List = []
x.add(1)
let y: ??? = x[0]

In this case, what is ???? In ye olden Java days (i.e. before Java 5), you used to have to insert a cast to int. However, this problem is much more elegant if you have generics. In fact, a lot more problems are made much easier with generics. How did Go (and other statically typed languages without generics) circumvent this problem and make life easier for the coder (ironic, as Go was supposed to make coding simpler?)?

$\endgroup$
2
  • $\begingroup$ I admit the question is a bit vague, I have an objective question in my head but can't exactly find a good wording for it. Feel free to suggest alternative formulations. $\endgroup$
    – Seggan
    Commented Jun 7, 2023 at 16:08
  • $\begingroup$ I know C has void pointers and casting, but I don't know enough about C to post about it. $\endgroup$
    – bigyihsuan
    Commented Jun 8, 2023 at 15:49

5 Answers 5

9
$\begingroup$

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.

$\endgroup$
9
$\begingroup$

AFAIK, most Gophers tend to replace generics with duplicating classes, merely changing the contained type and the name to reflect it. A similar approach is used in C, which has macros and therefore you can do something like this

#define INSTANTIATE_LIST(T) struct LinkedList_##T { T item; LinkedList_##T *next; }

INSTANTIATE_LIST(int)

LinkedList_int head = {0, NULL};

Using a buildsystem you can do something similar with golang's code generation tools: https://go.dev/blog/generate

$\endgroup$
2
  • $\begingroup$ So, essentially, roll your own templates? $\endgroup$ Commented Jun 8, 2023 at 3:00
  • 1
    $\begingroup$ @UnrelatedString yeah, not the greatest solution but people definitely do it in C and Go. $\endgroup$
    – kouta-kun
    Commented Jun 8, 2023 at 15:10
6
$\begingroup$

Go always had generics!
Except they were limited to built-in types and had special syntax support.

Generic arrays are spelt [n]a, slices []a, generic maps map[k]v, and channels chan a.
They also are created either by special literals or by the make feature.

This makes your example look like

x := make([]int)
x = append(x, 1)
var y int
y = x[0]

Now, how to have generic user types is a question Go (and other similar languages) just didn't answer, instead opting to leaving you to your own devices.

$\endgroup$
5
$\begingroup$

Using interface{} and type assertions

Go allows for dynamic dispatch using interfaces, which contain the methods needed for a type to implement said interface. As a special case, the empty interface interface{} specifies that a type needs to match no methods to be considered as implementing the empty interface; therefore, all types implement the empty interface.

Through this, pseudo-generics and heterogenous data structures (like JSON's heterogenous arrays) are possible.

This allows something like the following, where an interface{} is used to store values of different types in a slice. These types can be differentiated using a switch over the type, or with type assertions to a different type.

nodes := []any{12345, "hello world!", 5.678}
// note: any is a builtin alias for interface{}

for _, n := range nodes {
    switch n := n.(type) {
        case int: println(n-1) // n is an int
        case string: println(n + "foobar") // n is a string
        default: println("unknown") // everything else
    }
}

However, with type assertions, you will get a runtime error if you are unable to explicitly cast between the two types. In the above example, nodes[1].(int) will panic because a string cannot be casted into an int.

$\endgroup$
2
  • $\begingroup$ That looks messy $\endgroup$
    – Seggan
    Commented Jun 8, 2023 at 15:40
  • $\begingroup$ @Seggan because it is haha. Though, as other answers have mentioned, the builtin slice/array, map, and channel types are enough for most needs. Anything more complicated tends to be placed into structs. $\endgroup$
    – bigyihsuan
    Commented Jun 8, 2023 at 15:44
3
$\begingroup$

Disclaimer: I don't have much experience in Go, and this answer is written from the limited experience I have. If I've got anything wrong in this answer, please leave a comment and I'll try to correct the answer.


Go doesn't have a list type

That's right. You only have arrays, which need an element type:

x := []int {1, 2, 3, 4};

Nested arrays work the same way:

x := [][]int {{1, 2}, {3, 4}, {5, 6}};

Then, it's obvious to the compiler what type x[0] is. This means you don't need to explicitly specify the type. This would be perfectly fine:

y := x[0];

But what if it did?

The only option that I can think of is to get the programmer to explicitly put the type in the definition, like this:

var y int = x[0];

And then at runtime try and convert x[0] to an integer. If that fails, raise a runtime error.

This isn't great though, so it's probably better just to stick to arrays, and not lists.

$\endgroup$
2
  • 2
    $\begingroup$ I think this answer is side-stepping the question. Lists are an example where generics are useful, and it follows from the fact that Go has no generics that Go has no generic list type. The question is how can you have things like lists in Go, when those things would require generics in other languages; so "the standard library doesn't have them" isn't an answer. If the answer is "you can't have them" then it would be better to spell that out, but in that case the answer is really that Go doesn't circumvent the lack of generics, and that this is a major limitation in the language. $\endgroup$
    – kaya3
    Commented Jun 7, 2023 at 16:36
  • 2
    $\begingroup$ OTOH, if the answer is "you can't have them, but you don't need them because XYZ", then it might not be a major limitation in the language. But if XYZ is just arrays then that is still a pretty major limitation, because arrays aren't a good substitute for lists, and arrays aren't a substitute at all for many other things where generics are useful. $\endgroup$
    – kaya3
    Commented Jun 7, 2023 at 16:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .