Introduction: Why Generics in Go Are a Breakthrough
For a long time, the Go language (Golang) remained one of the few modern languages without support for generic programming (generics). Developers were forced to use interface{} and type casting, which led to a loss of type safety at compile time and cluttered the code. With the release of Go version 1.18 (February 2022), the situation changed dramatically.
Generics are a mechanism that allows you to write functions and data structures that can work with any types while maintaining strict typing. In this article, we will break down why they are needed and show with practical examples how to use them in everyday Go development.
Why Are Generics Needed in Go?
Before the advent of generics, developers used interface{} (empty interface) to create universal functions. Let's look at a classic example — a function that finds the minimum element in a slice:
// Without generics (Go < 1.18)func MinInt(a, b int) int { if a < b { return a } return b}
func MinFloat(a, b float64) float64 { if a < b { return a } return b}For each type, a separate function had to be written. With interface{}, it was possible to do this:
func MinInterface(a, b interface{}) interface{} { // Requires type casting and runtime checks // This is unsafe and inefficient}The problems with this approach are obvious:
- Loss of type safety — an error may only appear at runtime.
- Need for type casting — the code becomes cumbersome.
- Reduced performance — working with interfaces requires additional operations.
Generics solve all these problems. They allow you to:
- Write universal algorithms once.
- Maintain strict type checking at compile time.
- Eliminate type casting and improve performance.
Basics of Generics Syntax in Go
Generics in Go are based on type parameters. The syntax is simple and intuitive. Type parameters are specified in square brackets after the function or type name.
1. A Simple Function with Generics
Let's write the same minimum function, but now using generics:
package main
import "fmt"
// Min returns the minimum value of twofunc Min[T comparable](a, b T) T { if a < b { return a } return b}
func main() { fmt.Println(Min[int](10, 20)) // 10 fmt.Println(Min[float64](3.14, 2.71)) // 2.71 fmt.Println(Min[string]("abc", "xyz")) // abc}Here [T comparable] is the type parameter. comparable is a built-in constraint that guarantees the type supports the comparison operators == and !=. Note: for the < operator, you need to use the constraints.Ordered constraint (from the golang.org/x/exp/constraints package) or write your own.
2. Constraints
Constraints are interfaces that define which methods and operations types must support. In Go 1.18+, the constraints package appeared (currently in golang.org/x/exp), but you can create your own constraints.
import "golang.org/x/exp/constraints"
// MinOrdered works only with ordered typesfunc MinOrdered[T constraints.Ordered](a, b T) T { if a < b { return a } return b}A custom constraint for types that support the String() method: