Generics
Generics (type parameters) let you define reusable functions and types that are abstracted over types. Introduced in Go 1.18, generics significantly improve code reusability.
Syntax
// Defines a function with a type parameter.
func FuncName[T Constraint](arg T) T {
// process
}
// Defines a struct with a type parameter.
type TypeName[T Constraint] struct {
Field T
}
// Defines multiple type constraints (expressed as an interface).
type ConstraintName interface {
Type1 | Type2 | Type3
}
Common Type Constraints
| Constraint | Description |
|---|---|
| any | Accepts any type. Equivalent to interface{}. |
| comparable | Restricts to types that can be compared with == and != (slices and maps are not included). |
| ~T | Includes all types whose underlying type is T (including custom-defined types). |
| int | float64 | A union type constraint. Accepts any one of the specified types. |
| constraints.Ordered | Types that support ordering comparisons, defined in the golang.org/x/exp/constraints package. |
Sample Code
package main
import "fmt"
// Defines a constraint for numeric types.
type Number interface {
~int | ~int32 | ~int64 | ~float32 | ~float64
}
// A generic sum function.
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// A generic map function that transforms each element.
func Map[T, U any](slice []T, f func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = f(v)
}
return result
}
// A generic filter function.
func Filter[T any](slice []T, pred func(T) bool) []T {
var result []T
for _, v := range slice {
if pred(v) {
result = append(result, v)
}
}
return result
}
// A generic stack struct with a type parameter.
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
last := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return last, true
}
// A generic function using the comparable constraint.
func Contains[T comparable](slice []T, item T) bool {
for _, v := range slice {
if v == item {
return true
}
}
return false
}
func main() {
// Use the Sum function with different types.
ints := []int{1, 2, 3, 4, 5}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(ints)) // Prints "15".
fmt.Println(Sum(floats)) // Prints "6.6".
// Use the Map function to convert integers to strings.
nums := []int{1, 2, 3}
strs := Map(nums, func(n int) string {
return fmt.Sprintf("item%d", n)
})
fmt.Println(strs) // Prints "[item1 item2 item3]".
// Use the Filter function to extract even numbers.
evens := Filter(ints, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // Prints "[2 4]".
// Use the generic stack.
s := &Stack[string]{}
s.Push("Go")
s.Push("Generics")
v, ok := s.Pop()
fmt.Println(v, ok) // Prints "Generics true".
// Use the Contains function on a string slice.
langs := []string{"Go", "Python", "Rust"}
fmt.Println(Contains(langs, "Go")) // Prints "true".
fmt.Println(Contains(langs, "Java")) // Prints "false".
}
Notes
Generics let you avoid writing the same logic repeatedly for different types. For example, you can process both integer slices and float slices with a single function. Type inference usually allows you to call generic functions without explicitly specifying type parameters.
Functions with type parameters cannot be defined as methods. Type parameters can only be added to functions and type definitions.
The comparable constraint restricts to types that support == comparison (such as int, string, and structs). Slices and maps are not comparable and are excluded. Overusing generics can make code harder to follow, so it is recommended to use them only where a clear improvement in type safety is expected.
If you find any errors or copyright issues, please contact us.