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.goWe 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 testGeneric 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.