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.