Generics in Go: How Generic Programming Changes the Game

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

It All Started with Go 1.18: Why Generics Are More Than Just a Trendy Feature?

\n\n

For a long time, the Go community prided itself on the language's simplicity. The absence of generic programming (generics) was considered a deliberate choice: "we don't need complex templates, we write clear code." But reality took its toll. Developers got tired of copy-pasting functions for int, string, and float64 — or using interface{} with subsequent type assertions, losing compile-time safety.

\n\n

The release of Go 1.18 (2022) was a revolution. Generics added type parameters and constraints to the language. Now Go programmers can write generic data structures and algorithms without sacrificing performance and strict typing.

\n\n

In this article on pythonlib.ru, we'll explore how generics work in Go, see real code examples, and understand when to use them and when not to. Let's go!

\n\n

What Are Generics in Go in Simple Terms?

\n\n

Imagine you're writing a function that finds the minimum element in a slice. Without generics, you'd have to create three versions: for int, float64, and string. Or use interface{} with type assertions — slow and unsafe.

\n\n

Generics allow you to write one function that works with any type satisfying certain conditions. The syntax is simple: after the function name, type parameters are specified in square brackets.

\n\n

Syntax: Square Brackets and the Word any

\n\n
package main\n\nimport "fmt"\n\n// Min — generic function for finding the minimum\nfunc Min[T any](a, b T) T {\n    // Error: operator < is not defined for any\n    // if a < b {\n    //     return a\n    // }\n    return b\n}
\n\n

The code above won't compile because any is an empty interface (synonym for interface{}). The compiler doesn't know if the type supports the < operator. You need to add a constraint.

\n\n

Type Constraints: constraints

\n\n

In Go, constraints are defined through interfaces. You can use built-in ones from the golang.org/x/exp/constraints package or write your own.

\n\n
import (\n    "fmt"\n    "golang.org/x/exp/constraints"\n)\n\n// Min works with any ordered type\nfunc Min[T constraints.Ordered](a, b T) T {\n    if a < b {\n        return a\n    }\n    return b\n}\n\nfunc main() {\n    fmt.Println(Min(10, 20))       // 10\n    fmt.Println(Min(3.14, 2.71))   // 2.71\n    fmt.Println(Min("apple", "banana")) // "apple"\n}
\n\n

Note: constraints.Ordered includes all types for which the operators <, <=, >, >= are defined — these are int, float, string.

\n\n

Practical Examples: From Simple to Complex

\n\n

Let's see how generics in Go simplify writing real code.

\n\n

1. Generic Stack

\n\n

Without generics, you'd have to store []interface{} and do type assertions. With generics — elegant and safe.

\n\n
package main\n\nimport "fmt"\n\n// Stack — generic data structure\ntype Stack[T any] struct {\n    items []T\n}\n\nfunc (s *Stack[T]) Push(item T) {\n    s.items = append(s.items, item)\n}\n\nfunc (s *Stack[T]) Pop() (T, bool) {\n    if len(s.items) == 0 {\n        var zero T\n        return zero, false\n    }\n    item := s.items[len(s.items)-1]\n    s.items = s.items[:len(s.items)-1]\n    return item, true\n}\n\nfunc main() {\n    intStack := Stack[int]{}\n    intStack.Push(1)\n    intStack.Push(2)\n    val, ok := intStack.Pop()\n    fmt.Println(val, ok) // 2 true\n\n    strStack := Stack[string]{}\n    strStack.Push("hello")\n                

Blogs

Book Recommendations