Generics were introduced in Go starting from version 1.18. For a long time, Go was considered a conservative language, where generics were excluded for the sake of simplicity, but with the growing number of projects and the development of the ecosystem, there arose a need for writing universal functions and structures. This is especially critical for container structures, collection processing algorithms, and infrastructure code.
Problem: Before the introduction of generics, it was necessary to duplicate code or use empty interfaces (interface{}), which led to a loss of type safety and reduced performance, as well as complicating debugging.
Solution: Generics are implemented in Go using type parameters, which are specified in square brackets for functions and types. With constraints, it is possible to restrict the allowable type parameters. This allows writing generic functions without losing type safety.
Example code:
package main import "fmt" type Adder[T any] func(a, b T) T func Sum[T any](slice []T, add Adder[T]) T { var result T for _, v := range slice { result = add(result, v) } return result } func intAdder(a, b int) int { return a + b } func main() { nums := []int{1, 2, 3, 4} sum := Sum(nums, intAdder) fmt.Println(sum) }
Key features:
Can arithmetic operations (+, -, *, /) be used for any type parameter T in generics?
No. The Go compiler does not know whether the type parameter supports arithmetic. To do this, it is necessary to specify a constraint, for example, an interface with operator constraints starting from Go 1.18+.
Example code:
type Addable interface { int | float64 | uint } func Sum[T Addable](slice []T) T { var result T for _, v := range slice { result += v } return result }
Can generic containers contain methods with different behavior for different types?
No. Methods of generic structures or functions are the same for all types unless using type switch inside the method. Behavior should be defined through constraints or purely parameterized.
Can types with type parameters (generic types) be created at the package level, not just for functions?
Yes, starting from Go 1.18, it is possible to create both generic functions and generic struct types:
type Stack[T any] struct { items []T } func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
Inside a library for working with collections, universal Map functionality was implemented through interface{}:
Pros:
Cons:
In the same project, they switched to generics and set constraints through interfaces:
Pros:
Cons: