It All Started with Go 1.18: Why Generics Are More Than Just a Trendy Feature?
\n\nFor 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.
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\nIn 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\nWhat Are Generics in Go in Simple Terms?
\n\nImagine 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.
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\nSyntax: Square Brackets and the Word any
\n\npackage 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\nThe 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.
Type Constraints: constraints
\n\nIn Go, constraints are defined through interfaces. You can use built-in ones from the golang.org/x/exp/constraints package or write your own.
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\nNote: constraints.Ordered includes all types for which the operators <, <=, >, >= are defined — these are int, float, string.
Practical Examples: From Simple to Complex
\n\nLet's see how generics in Go simplify writing real code.
\n\n1. Generic Stack
\n\nWithout generics, you'd have to store []interface{} and do type assertions. With generics — elegant and safe.
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