Generic Types
So far the type parameters have lived on functions. But a type can be
generic too. That's how you build reusable containers — stacks, queues, trees,
caches — that hold a specific element type without falling back to
interface{} and type assertions.
Parameterizing a struct
Put the type parameter list right after the type name:
type Stack[T any] struct {
items []T
}
Stack is now a generic type. It isn't a usable type by itself — you
instantiate it by supplying the argument, and each instantiation is a
distinct, fully checked type:
var ints Stack[int] // items is []int
strs := Stack[string]{} // items is []string
A Stack[int] and a Stack[string] are unrelated types; you can't
accidentally push a string onto the int stack. The [T any] here uses any
because the stack only stores and returns values — it never compares or adds
them, so it needs nothing more.
Methods use the receiver's parameter
Methods on a generic type name the type parameter in the receiver, then use it freely in the signature and body:
func (s *Stack[T]) Push(v T) {
s.items = append(s.items, v)
}
Two rules to internalize:
- The receiver is
*Stack[T](orStack[T]) — you repeat the parameter name so the method can refer toT. - A method cannot introduce new type parameters of its own. It works only
with the ones the type already declares. (If you need a fresh parameter — say
a
Mapthat changes element type — that has to be a free function, not a method.)
The zero value of T
Sometimes a method must produce a T out of nothing — for example, what does
Pop return when the stack is empty? You can't invent a value, but every type
has a zero value:
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
last := len(s.items) - 1
v := s.items[last]
s.items = s.items[:last]
return v, true
}
Returning (T, bool) is the idiomatic "value plus ok" shape — the caller
checks the bool rather than trusting a possibly-zero value. var zero T gives
the correct empty result whether T is int (0), string (""), or a
struct.
Why Go does it this way
Before generic types, a general-purpose stack meant []interface{}: every
Push boxed the value and every Pop needed a type assertion that could panic
at run time. A Stack[T] erases none of that type information — the element
type is baked into the instantiation, the compiler checks every call, and
there's zero boxing or asserting. You write the data structure once and reuse
it at any element type with full safety. In the exercise you'll finish exactly
this stack: Push, Pop, and Len, tested at both int and string.
Practice
1. A Generic Stack
IntermediateImplement a generic LIFO (last-in, first-out) stack.
The starter defines the type and the three method stubs:
type Stack[T any] struct { items []T } func (s *Stack[T]) Push(v T) func (s *Stack[T]) Pop() (T, bool) func (s *Stack[T]) Len() intBehavior:
Push(v)addsvto the top of the stack.Pop()removes and returns the top value andtrue. On an empty stack it returns the zero value ofTandfalse— do not panic.Len()returns the number of items currently held.
The receiver is a pointer (
*Stack[T]) becausePushandPopmutate the stack. Use the slice as the backing store: append to push, and re-slice off the last element to pop.The tests exercise the stack with
intfirst — pushing several values, checkingLen, popping them back in reverse order, and popping an empty stack for(0, false)— then repeat the whole thing withstringto prove the same code works at any element type.package main import "fmt" // Stack is a generic last-in, first-out stack of T. type Stack[T any] struct { items []T } // Push adds v to the top of the stack. func (s *Stack[T]) Push(v T) { // TODO: implement } // Pop removes and returns the top value and true. If the stack is empty it // returns the zero value of T and false. func (s *Stack[T]) Pop() (T, bool) { var zero T return zero, false // TODO: implement } // Len reports how many items are currently on the stack. func (s *Stack[T]) Len() int { return 0 // TODO: implement } func main() { var s Stack[int] s.Push(1) s.Push(2) fmt.Println("len:", s.Len()) v, ok := s.Pop() fmt.Println("pop:", v, ok) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.