Playing with Go Generics

Filed under golang on November 13, 2021

Go 1.17 dropped a while ago, and a workmate at the time was quick to notice that with it had dropped the long awaited generics. Generics were something I used a lot of in the Java and .NET worlds, but being in the Go world for the last 5 or so years has taught me that they’re not entirely necessary.

However, if this is to become a staple feature in my language of choice in 1.18, then I should probably learn to use it. I may run across that 0.01% of problems that require it one day.

Enabling the experimental feature

In order to get generics working, we first need to pass the go tool a special flag

$ go run -gcflags=-G=3 ./main.go

We should now be able to use the new feature. Currently, you can’t export generics from a package and the compiler will throw an error if you try.

Generic Functions

Go’s generic syntax is quite nice and clean, opting for square brackets to denote a generic member

Functions look like the following

func print[T any](x T) {
  fmt.Printf("%v\n", x)
}

Take note of the any keyword here, which is any type within go. We can then call this like any other regular function

func main() {func main() {
	print("test1")
	print(1)
	print(errors.New("error test"))
}

Which will print what we expect

test1
1
error test

Generic Struct Members

Similarly, we can use generics in our struct fields. This differs a bit from generic functions as the instantiated struct must specify the contained type.

type genericTest[T any] struct {
	someField T
}

func main() {
	a := genericTest[string]{"test"}
	b := genericTest[int]{1}
	c := genericTest[float64]{2.4}
	fmt.Printf("%v\n", a.someField)
	fmt.Printf("%v\n", b.someField)
	fmt.Printf("%v\n", c.someField)
}

And with a function receiver

type genericTest[T any] struct {
	someField T
}

func (g genericTest[any]) print() {
	fmt.Printf("%v\n", g.someField)
}

func main() {
	a := genericTest[string]{"test"}
	b := genericTest[int]{1}
	c := genericTest[float64]{2.4}
	a.print()
	b.print()
	c.print()
}

Type Constraints

The type within the square brackets can be any Golang type, meaning Go will ensure that whatever is in the generic fits that type contract.

type Foo interface {
  Bar()
}

type Bar struct {}

func (b Bar) Bar() {
  fmt.Printf("test")
}

type baz[T Foo] struct {
  foobar T
}

func main() {
  b := Bar{}

  f := baz[Bar] { b }

  fmt.Printf("%v\n", f)

  a := 0
  // COMPILE ERROR, `int` does not implement the Foo interface
  g := baz[int] {a}
}

We might also have a situation where maybe we want to use something like a + operator.

type foo[T any] struct {
  a T
  b T
}

// COMPILE ERROR, `T` doesn't necessarily support the `+` operator
func (f *foo[T]) Add[T]() T {
  return f.a + f.b
}

We can handle this kind of situation with the following syntax

type Addable interface {
	type int, float64
}

type foo[T Addable] struct {
  a T
  b T
}

func (f *foo[Addable]) Add() T {
  return f.a + f.b
}

The constraints package is going to provide a few useful types for us to use with generics.

Further Tinkering

I tried to see if I could do a few other things, such as function overloading

type genericTest[T any] struct {
	someField T
}

func (g genericTest[string]) print() {
	fmt.Printf("oh boy a string %v\n", g.someField)
}

func (g genericTest[int]) print() {
	fmt.Printf("oh boy a number %v\n", g.someField)
}

func (g genericTest[float64]) print() {
	fmt.Printf("oh boy a number %v\n", g.someField)
}

func main() {
	a := genericTest[string]{"test"}
	b := genericTest[int]{1}
	c := genericTest[float64]{2.4}
	a.print()
	b.print()
	c.print()
}

Unfortunately, I was greeted with the usual “function redeclared” compiler error. Oh well. Not a big deal, I’ve dealt with having no function overloading in golang until now, I’m sure I can continue without it

Final thoughts

Still not convinced I need these, I’m yet to run into a circumstance that can’t be solved with normal go types. I guess time will tell, maybe it’ll see some more adoption once they reimplement the standard library, but I’m going to keep going the way I have been for now.


Stephen Gream

Written by Stephen Gream who lives and works in Melbourne, Australia. You should follow him on Minds