Go 1.18+ supports generics using type parameters, allowing you to write functions and types that work with any type satisfying specific constraints. You define these constraints using the type keyword with interface{} or specific method sets, then apply them to your function or struct definitions using square brackets.
Here is a practical example of a generic function that finds the maximum value in a slice, constrained to types that support comparison:
package main
import "fmt"
// Max returns the largest value in a slice of comparable types
func Max[T comparable](items []T) T {
if len(items) == 0 {
panic("slice cannot be empty")
}
max := items[0]
for _, item := range items[1:] {
if item > max {
max = item
}
}
return max
}
func main() {
// Works with integers
fmt.Println(Max([]int{1, 5, 3})) // Output: 5
// Works with strings
fmt.Println(Max([]string{"apple", "banana", "cherry"})) // Output: cherry
}
You can also define custom constraints to enforce specific behavior, such as requiring a type to have a Len() method. This is useful for writing generic sorting or searching algorithms:
// Define a custom constraint
type Sizer interface {
Len() int
}
// A generic function accepting any type implementing Sizer
func GetLength[T Sizer](s T) int {
return s.Len()
}
type MySlice []string
func (m MySlice) Len() int { return len(m) }
func main() {
s := MySlice{"a", "b", "c"}
fmt.Println(GetLength(s)) // Output: 3
}
Key things to remember: the type parameter name (like T) is local to the function or type definition, and you must specify the constraint immediately after the parameter name. If you need to use the type in a struct, define the type parameters on the struct itself, like type Container[T any] struct { value T }. Generics are fully type-checked at compile time, so you don't lose type safety compared to using interface{} and type assertions.