DEV Community

Gonçalo Rodrigues
Gonçalo Rodrigues

Posted on

Using Go Generics for cleaner code

Go has been surviving for years without generics. The latest major version update (Go 1.18) finally introduced them and I think they're amazing at making Go code look much cleaner! Here are some examples where I found it useful:

1 - Unmarshaling JSON strings

One of the examples that everyone has seen before in Go is unmarshaling a JSON string into a Go struct:

str := `{"page": 1, "fruits": ["apple", "peach"]}`
res := response{}
err := json.Unmarshal([]byte(str), &res)
Enter fullscreen mode Exit fullscreen mode

What I don't like about this example is that you have to pass an output parameter as input (&res). Go supports multiple output parameters so we should never have to do this.

With generics, you can implement a function that instead initializes the right type and returns it:

func unmarshal[T any](data []byte) (*T, error) {
    out := new(T)
    if err := json.Unmarshal(data, out); err != nil {
        return nil, err
    }
    return out, nil
}
str := `{"page": 1, "fruits": ["apple", "peach"]}`
res, err := unmarshal[response]([]byte(str))
Enter fullscreen mode Exit fullscreen mode

2 - Type safe containers

In go, if you want to implement a linked list without generics, it would look something like this:

type Node struct {
    prev  *Node
    next  *Node
    value interface{}
}

type List struct {
    head *Node
    tail *Node
}
Enter fullscreen mode Exit fullscreen mode

It's not type safe! It uses interface{} and will require casts all over the code.
Or you can also implement a very specific LinkedList for integers, or for strings, but not one that can work for any type.

With generics, you can have an abstract LinkedList that works for any type and it's still type safe:

type Node[T any] struct {
    prev  *Node[T]
    next  *Node[T]
    value T
}

type List[T any] struct {
    head *Node[T]
    tail *Node[T]
}
Enter fullscreen mode Exit fullscreen mode

3 - Slice utilities

One of the most frustrating things in Go is to iterate over a slice to perform a basic operation. For example, if you want to check if a certain string is in slice of strings, this is how you do it:

func contains(strings []string, s string) bool {
    for _, s := range strings {
    if s == string {
        return true
    }
    return false
}

exists := contains([]string{"a", "b"}, "a")
Enter fullscreen mode Exit fullscreen mode

However, it only works for strings! If you want to do the same thing with integers, or structs, you have to reimplement this logic everywhere.

With generics, the standard lib introduces a much needed utility that works with any slice of comparable types:

exists := slices.Contains([]string{"a", "b"}, "a")
Enter fullscreen mode Exit fullscreen mode

Conclusion

I love Go. We use it a lot at Multy. I think generics were one of the missing pieces to make it a really good language, but they are controversial in the Go community. What is your opinion on them?

Top comments (0)