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

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 did they appear in Go?

\n

For a long time, the Go language (Golang) remained one of the few modern languages without support for generic programming (generics). Developers had to either duplicate code for different data types or use interfaces and empty interface{}, which led to a loss of type safety and the need to perform type casting at runtime.

\n

With the release of Go version 1.18 (February 2022), the long-awaited generics appeared in the language. Generics is a mechanism that allows you to write functions, types, and data structures that work with arbitrary types while maintaining strict typing at compile time.

\n

In this article, we will cover:

\n
    \n
  • Why generics are needed in Go;
  • \n
  • How to declare generic functions and types;
  • \n
  • What constraints are and how to use them;
  • \n
  • Practical usage examples.
  • \n
\n\n

Why are Generics needed in Go?

\n

Before the advent of generics, a typical problem looked like this: you need to write a function that works with slices of different types (e.g., int, string, float64).

\n

Without generics you had to either write three separate functions:

\n
func SumInts(nums []int) int {\n    total := 0\n    for _, v := range nums {\n        total += v\n    }\n    return total\n}\n\nfunc SumFloats(nums []float64) float64 {\n    total := 0.0\n    for _, v := range nums {\n        total += v\n    }\n    return total\n}\n
\n

Or use interface{} with type casting — unsafe and inconvenient.

\n

With generics one function is enough:

\n
func Sum[T int | float64](nums []T) T {\n    var total T\n    for _, v := range nums {\n        total += v\n    }\n    return total\n}\n
\n

The advantages are obvious:

\n
    \n
  • Type safety — type errors are caught at compile time;
  • \n
  • Reduced code duplication — one function for many types;
  • \n
  • Readability — the code becomes more concise and understandable.
  • \n
\n\n

Basics of Generics syntax in Go

\n

Declaring a generic function

\n

The basic syntax uses square brackets [] to specify type parameters:

\n
func FunctionName[TypeName constraint](parameters) result {\n    // function body\n}\n
\n

Example of a simple function that returns the passed value:

\n
func Identity[T any](value T) T {\n    return value\n}\n\nfunc main() {\n    fmt.Println(Identity[int](42))       // 42\n    fmt.Println(Identity[string]("Go"))  // Go\n}\n
\n

Go can automatically infer the type, so explicitly specifying [int] is optional:

\n
fmt.Println(Identity(42))       // Go will understand that T = int\nfmt.Println(Identity("Go"))    // T = string\n
\n\n

Generic types (structs)

\n

Generics work not only with functions but also with custom types:

\n
type Stack[T any] struct {\n    elements []T\n}\n\nfunc (s *Stack[T]) Push(value T) {\n    s.elements = append(s.elements, value)\n}\n\nfunc (s *Stack[T]) Pop() (T, bool) {\n    if len(s.elements) == 0 {\n        var zero T\n        return zero, false\n    }\n    value := s.elements[len(s.elements)-1]\n    s.elements = s.elements[:len(s.elements)-1]\n    return value, true\n}\n\nfunc main() {\n    intStack := Stack[int]{}\n    intStack.Push(10)\n    intStack.Push(20)\n    val, ok := intStack.Pop()\n    fmt.Println(val, ok) // 20 true\n}\n
\n\n

Constraints: type restrictions

\n

In the examples above, we used any — a built-in constraint meaning "any type". However, it is often necessary to impose restrictions: for example, that the type supports comparison or arithmetic operations.

\n

Built-in

Blogs

Book Recommendations