Generics, and when not to · 3 MIN READ

A type can take parameters too

Write a container that holds one type rather than any type.

Types take parameters with the same syntax: type Stack[T any] struct { items []T }. Methods on it repeat the parameter in the receiver — func (s *Stack[T]) Push(v T) — which makes the type's parameter available to the method.

For most of generics' life a method could not introduce a parameter of its own, so transformations to another type were written as functions before Go 1.27. That restriction is gone; the next lesson is about what replaced it.

The zero value still has to work. var s Stack[int] gives you a struct whose slice is nil, and appending to a nil slice is fine, so the stack is usable without a constructor — worth designing for rather than stumbling into.

Instantiation is explicit where inference has nothing to work from: Stack[int]{} names the type, while Push(1) infers nothing because T is already fixed by the receiver.

SEE THE IDEA

The receiver carries the container’s type argument

  1. 01

    Instantiate

    go
    Stack[string]

    Fixes the stored element type

  2. 02

    Receiver

    go
    func (s *Stack[T]) Push(v T)

    Reuses T from the receiver

  3. 03

    Zero value

    go
    var s Stack[int]

    Design operations to accept nil storage

Each instantiation keeps its element type through every operation.

In practice

A generic container should preserve the caller’s type and provide a clear empty-state contract. Return a value and a boolean when an element may be absent. Clearing removed pointer-bearing elements can avoid retaining objects through the backing slice after a pop.

Watch out

The zero value is useful only if the implementation supports it. Test an empty container before adding constructor requirements.

Official reference ↗

Work through the exercise

go
package main

// A Stack that holds one element type. Push adds, Pop returns the
// most recently added value and whether there was one. The zero
// value must be usable without a constructor.

func main() {
	var s Stack[string]
	s.Push("go")
	value, ok := s.Pop()
	println(value, ok)
}
Try it yourself

Trace each value and write down your reasoning. Copy the source into GopherForge or your local Go environment to compile and check it. This web page does not execute your code or send it to a compiler service.

Progress stays in this browser. No account needed.