home

Basic observations of Generics in Go

Mar 03, 2022

Generics are coming in Go 1.18, so I decided to update my somewhat popular LRU cache. For the most part, it was straightforward, but there were a few minor things that stood out. I should point out that my last heavy use of generics was over a decade ago in C# (and from what I remember, C# had a pretty great implementation).

Generic Methods

You can't have generic methods. The first example works, but the second won't compile:

func ListContains[T comparable](needle T, haystack []T) bool {
  ...
}

// compiler error: method must have no type parameters
func (u Utils) ListContains[T comparable](needle T, haystack []T) bool {
 ...
}

This shatters my belief that the second version is just syntactical sugar for ListContains[T comparable](u utils, needle T, haystack []T) bool. I should probably read and understand how generics are implemented.

Type Assertion

You cannot switch on a generic type. Give value T, you need to do:

if reader, ok := (interface{})(value).(Reader); ok {
  // use reader
}

This is minor, but, in my opinion, unusual given how interfaces are used in Go. Hopefuly we'll see this addressed soon (you can follow these two issues 45380 and 49206).

default(T)

I couldn't find a way to return the default value for a parameter type. In C# you can do default(T) to get the default value for T (i.e false for bool, 0 for int, ...).

UPDATE Ok, that was silly, you can just do:

var dflt T
return dflt

Generic Containers (or the lack thereof)

There's no generic container in the standard library. For example, you won't find a generic container.List[T] or sync.Map[T]. According to this insightful issue they wanted to minimize the scope of the changes.

any

Instead of interface{}, you can use any. This makes generic definitions more concise, e.g. Node[T any] instead of Node[T interface{}]. You can use it in non-generic code as well (the commit which mass-renamed them in the Go codebase is 2580d0e08).

This works:

m := map[string]any{
  "over": 9000,
  "name": "goku",
}
fmt.Println(m)

Inference

I didn't have any expectations, but inference works for functions, but not types:

// Works for a function
func inspect[T any](value T) { ... }

// infers T: int
inspect(9000)


// But not a type
type Node[T any] struct {
  Value T
}

// does not infer T: string
// compiler error: cannot infer T
n := Node{Value: "Leto"}

// must be explicit
n := Node[string]{Value: "Leto"}

Tooling

You'll obviously want to update your tooling. I don't use much, but I needed the latest goimports to make my basic setup work.