Generics in Go: why they are needed and how to use them

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: What are Generics and why they appeared in Go



For a long time, the Go community managed without generics (parameterized types). Developers used interface{} and manual type casting, which led to cumbersome code and runtime errors. With the release of version Go 1.18 (February 2022), generics — one of the most anticipated features — were officially added to the language.



Generics (or parametric polymorphism) allow you to write functions and data structures that work with any types while maintaining strict typing at compile time. This means you can write one algorithm (e.g., sorting or searching) and use it for int, string, custom structs — without code duplication and without losing type safety.



In this article, we will explore why generics are needed in Go, how to use them correctly, and what pitfalls to consider.



Main part: How Generics work in Go



1. Type parameter syntax



The basic syntax of generics in Go looks like this: after the function or type name, type parameters are specified in square brackets. Let's look at a simple example — a function that returns the passed value (an identity analog):



package main

import "fmt"

// Generic function: T is a type parameterfunc Print[T any](value T) { fmt.Println(value)}

func main() { Print[int](42) // explicit type specification Print[string]("Go") // explicit type specification Print("Go 1.18") // implicit type inference}


The keyword any is an alias for interface{}. It means the function accepts a value of any type. However, the true power of generics is revealed when using constraints.



2. Constraints and interfaces



Constraints allow you to specify which operations can be performed on the type parameter. For example, to add two numbers, you need to guarantee that the type supports operators +, -, *, etc. In Go, this is done using special interfaces from the golang.org/x/exp/constraints package or custom interfaces:



package main

import ( "fmt" "golang.org/x/exp/constraints")

// Constraint: T must support comparisonfunc Max[T constraints.Ordered](a, b T) T { if a > b { return a } return b}

func main() { fmt.Println(Max(10, 20)) // 20 fmt.Println(Max("apple", "banana")) // banana // fmt.Println(Max([]int{1}, []int{2})) // Compilation error: slices are not ordered}


The built-in interface constraints.Ordered includes types that support operators <, <=, >, >=: all numeric types and string.



You can also create custom constraints:



// Custom constraint: the type must have a String() methodtype Stringer interface {    String() string}

func Describe[T Stringer](v T) string { return "Value: " + v.String()}


3. Generic structs and methods



Generics in Go work not only with functions but also with custom types. Let's look at a classic example — a generic stack:



package main

import "fmt"

// Generic struct Stacktype Stack[T any] struct { items []T}

// Push adds an element to the stackfunc (s *Stack[T]) Push(item T) { s.items = append(s.items, item)}

// Pop removes an element from the stackfunc (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T return zero, false } last := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return last, true}

func main()

Blogs

Book Recommendations