Generics in Go — How They Work and How to Play With Them

Chris Brown
Apr 8, 2020 · 4 min read

Go is a bit infamous for not supporting generics, but lately generics have come much closer to becoming a reality. There’s a draft design that seems to be relatively stable and is gaining traction in the form of a prototype source-to-source translator implemented by the Go team. Here’s what the latest design looks like and how you can try out generics yourself.


LIFO Stack

Let’s say you want to create a last-in, first-out stack. Without generics, you would probably implement it like this:

type Stack []interface{}

func (s Stack) Peek() interface{} {
return s[len(s)-1]

func (s *Stack) Pop() {
*s = (*s)[:len(*s)-1]

func (s *Stack) Push(value interface{}) {
*s = append(*s, value)

There’s a problem here though: Whenever you Peek at an item, you have to use a type assertion to convert it from interface{} to something useful. If your stack is a stack of *MyObject that means a lot of s.Peek().(*MyObject). Not only is that an eye-sore, but it invites room for error. What if you forget the asterisk? Or what if you push the wrong type? s.Push(MyObject{}) would happily compile, and you might not realize your mistake until it's impacting your service.

In general using interface{} is relatively dangerous. It's always safer to use more restricted types so issues can be discovered at compile-time instead of run-time.

Generics solves this problem by allowing types to have type parameters:

type Stack(type T) []T

func (s Stack(T)) Peek() T {
return s[len(s)-1]

func (s *Stack(T)) Pop() {
*s = (*s)[:len(*s)-1]

func (s *Stack(T)) Push(value T) {
*s = append(*s, value)

This adds a type parameter to Stack, eliminating the need for interface{} altogether. Now, when you Peek(), the value returned is already the original type and there's no chance of Pushing the wrong type of value. This implementation is all-around much safer and easier to use.

Additionally, generic code is generally easier for the compiler to optimize, resulting in better performance (at the expense of binary size). If we benchmark the above non-generic and generic code, we can see the difference:

type MyObject struct {
X int

var sink MyObject

func BenchmarkGo1(b *testing.B) {
for i := 0; i < b.N; i++ {
var s Stack
sink = s.Peek().(MyObject)

func BenchmarkGo2(b *testing.B) {
for i := 0; i < b.N; i++ {
var s Stack(MyObject)
sink = s.Peek()

In this case we allocate less memory and run at twice the speed using generics.


The stack example above works for any type. However, there are many cases where you need to write code that works only on types with certain traits. For example, you might want your stack to require that types implement the String() function. This is where "contracts" come in:

contract stringer(T) {
T String() string

type Stack(type T stringer) []T

// Now we can use the String method of T:
func (s Stack(T)) String() string {
ret := ""
for _, v := range s {
if ret != "" {
ret += ", "
ret += v.String()
return ret

More Examples

The above examples only cover the basics. You can also add type parameters to functions and add specific types to contracts.

For more examples, there are two places you can go to:

The Draft Design

The draft design contains a more detailed description as well as several more examples:

The Prototype CL

The prototype CL has several examples as well. Look for the files that end in “.go2”:

How You Can Try Generics Out Today

Using the WebAssembly Playground

By far the fastest and easiest way to try out generics is through the WebAssembly playground. Behind the scenes, it uses a WASM build of the prototype source-to-source translator to run Go code directly in your browser. This does have some restrictions though (see

Compiling The CL

The CL referenced above contains an implementation of a source-to-source translator that can be used to compile generic code to code that can be compiled by the current releases of Go. It refers to generic code (“polymorphic” code) as Go 2 code and non-polymorphic code as Go 1 code, but depending on the details of the implementation, generics could become part of a Go 1 release rather than a Go 2 release.

The CL expands the existing go/* packages to include support for generics and adds a new go/go2go package, which can be used to rewrite generic Go 2 code to Go 1 code.

It also adds a “go2go” command that can be used to translate code from your CLI.

You can compile the CL by following Go’s Installing Go from Source instructions. When you get to the optional “Switch to the master branch” step, checkout the CL instead:

git fetch "" refs/changes/17/187317/14 && git checkout FETCH_HEAD

Note that this will checkout patchset 14, which is the latest at the time of writing. Go to the CL and find the “Download” button to get the checkout command for the latest patchset.

After compiling the CL, you can use the go/* packages to write custom tooling for working with generics, or you can just use the go2go command-line tool:

go tool go2go translate mygenericcode.go2

Tempus Ex

We’re transforming the sports and media landscape.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store