DMarlon Studio

Lesson 9 · 9 of 20

A generic stack · Go

Build a generic Stack[T] type in Go with Push, Pop and Len, and use it with strings and with integers: an editor's undo history.

Go · A generic stack in DMarlon Studio: the example's code and its output in the terminal

Types can have type parameters too. `Stack[T any]` is a stack of items of type T, kept in a slice. Stack[string] and Stack[int] are two different types, but both come from one piece of code, with no copying and no any.

Methods repeat the parameter in the receiver: func (s *Stack[T]) Push(v T). The receiver is a pointer because Push and Pop change s.items; with a value receiver they would change a copy. One detail: in Go, methods cannot declare type parameters of their own, only use the type's.

Pop removes the last item (a stack is LIFO: last in, first out) and returns two values, the item and a bool. When the stack is empty it returns top untouched, which is the zero value of T ("" for strings, 0 for numbers), and false. That is why the fourth undo prints undo "" ok=false.

var history Stack[string] is ready to use as it is: its slice is nil, and append works on it. The edits are undone in the reverse order they were made, which is exactly what you expect from an undo button.

pages uses the same stack with integers, like a browser's history. &Stack[int]{items: ...} creates it with initial data; that works here because items is visible inside package main, even though it starts with a lowercase letter.

Code · main.go

package mainimport "fmt"// Stack is a generic type: Stack[string], Stack[int]... one implementation.type Stack[T any] struct{ items []T }func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }func (s *Stack[T]) Len() int { return len(s.items) }// Pop returns the zero value of T and false when the stack is empty.func (s *Stack[T]) Pop() (top T, ok bool) {	if len(s.items) == 0 {		return top, false // top still holds the zero value of T	}	top = s.items[len(s.items)-1]	s.items = s.items[:len(s.items)-1]	return top, true}func main() {	var history Stack[string] // the zero value is ready to use	for _, edit := range []string{"type 'Dear Ana'", "bold title", "insert logo"} {		history.Push(edit)	}	fmt.Println("edits in history:", history.Len())	for i := 0; i < 4; i++ {		edit, ok := history.Pop()		fmt.Printf("undo %q ok=%t\n", edit, ok)	}	pages := &Stack[int]{items: []int{1, 4, 9}} // the same code, now with ints	page, _ := pages.Pop()	fmt.Println("back from page", page, "| pages left:", pages.Len())}

Output · go run main.go

edits in history: 3
undo "insert logo" ok=true
undo "bold title" ok=true
undo "type 'Dear Ana'" ok=true
undo "" ok=false
back from page 9 | pages left: 2

Key points

  • A generic type is declared with type Stack[T any] struct.
  • Methods use the parameter in the receiver: (s *Stack[T]).
  • var zero T or a named result gives the zero value of T.
  • Returning (T, bool) avoids magic values on an empty stack.
  • Stack[string] and Stack[int] are different types from the same code.

Exercise

Add a Peek() (T, bool) method that returns the top item without removing it, and use it to print which edit would be undone next before each Pop.