Go Generics
Type parameters and generic programming in Go
🔧 What are Go Generics?
Go Generics allow you to write flexible, reusable code that works with different types. Introduced in Go 1.18, generics enable type-safe functions and data structures without code duplication.
// Generic function example
package main
import "fmt"
func Max[T comparable](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(10, 20)) // int
fmt.Println(Max(3.14, 2.71)) // float64
fmt.Println(Max("hello", "world")) // string
}
Output:
20
3.14
world
Generic Programming Features
Type Parameters
Define flexible function parameters
func Name[T any](param T) T
Type Constraints
Limit which types can be used
func Name[T comparable](param T)
Generic Types
Create reusable data structures
type Stack[T any] struct{}
Type Inference
Automatic type detection
result := Max(10, 20) // inferred
🔹 Generic Functions
Create functions that work with multiple types:
package main
import "fmt"
// Generic function with any constraint
func Print[T any](value T) {
fmt.Printf("Value: %v, Type: %T\n", value, value)
}
// Generic function with comparable constraint
func Equal[T comparable](a, b T) bool {
return a == b
}
// Generic function with custom constraint
func Sum[T int | float64](slice []T) T {
var sum T
for _, v := range slice {
sum += v
}
return sum
}
func main() {
// Using generic functions
Print(42)
Print("Hello")
Print(3.14)
fmt.Println("Equal:", Equal(5, 5))
fmt.Println("Equal:", Equal("go", "go"))
ints := []int{1, 2, 3, 4, 5}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println("Sum of ints:", Sum(ints))
fmt.Println("Sum of floats:", Sum(floats))
}
Output:
Value: 42, Type: int
Value: Hello, Type: string
Value: 3.14, Type: float64
Equal: true
Equal: true
Sum of ints: 15
Sum of floats: 6.6
🔹 Generic Data Structures
Build reusable data structures with generics:
package main
import "fmt"
// Generic Stack
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
}
index := len(s.items) - 1
item := s.items[index]
s.items = s.items[:index]
return item, true
}
func (s *Stack[T]) IsEmpty() bool {
return len(s.items) == 0
}
// Generic Map with custom key-value types
type SafeMap[K comparable, V any] struct {
data map[K]V
}
func NewSafeMap[K comparable, V any]() *SafeMap[K, V] {
return &SafeMap[K, V]{
data: make(map[K]V),
}
}
func (m *SafeMap[K, V]) Set(key K, value V) {
m.data[key] = value
}
func (m *SafeMap[K, V]) Get(key K) (V, bool) {
value, exists := m.data[key]
return value, exists
}
func main() {
// String stack
stringStack := &Stack[string]{}
stringStack.Push("first")
stringStack.Push("second")
item, ok := stringStack.Pop()
fmt.Printf("Popped: %s, Success: %t\n", item, ok)
// Integer stack
intStack := &Stack[int]{}
intStack.Push(10)
intStack.Push(20)
item2, ok2 := intStack.Pop()
fmt.Printf("Popped: %d, Success: %t\n", item2, ok2)
// Generic map
userMap := NewSafeMap[string, int]()
userMap.Set("alice", 25)
userMap.Set("bob", 30)
age, exists := userMap.Get("alice")
fmt.Printf("Alice's age: %d, Exists: %t\n", age, exists)
}
Output:
Popped: second, Success: true
Popped: 20, Success: true
Alice's age: 25, Exists: true
🔹 Type Constraints
Define custom constraints for more specific generic functions:
package main
import "fmt"
// Custom constraint using interface
type Numeric interface {
int | int32 | int64 | float32 | float64
}
// Custom constraint with methods
type Stringer interface {
String() string
}
// Generic function with numeric constraint
func Add[T Numeric](a, b T) T {
return a + b
}
// Generic function with method constraint
func PrintString[T Stringer](item T) {
fmt.Println("String representation:", item.String())
}
// Combining constraints
type Ordered interface {
int | int32 | int64 | float32 | float64 | string
}
func Min[T Ordered](slice []T) T {
if len(slice) == 0 {
var zero T
return zero
}
min := slice[0]
for _, v := range slice[1:] {
if v < min {
min = v
}
}
return min
}
// Custom type implementing Stringer
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}
func main() {
// Using numeric constraint
fmt.Println("Add integers:", Add(10, 20))
fmt.Println("Add floats:", Add(3.14, 2.86))
// Using method constraint
person := Person{Name: "Alice", Age: 30}
PrintString(person)
// Using ordered constraint
numbers := []int{5, 2, 8, 1, 9}
words := []string{"zebra", "apple", "banana"}
fmt.Println("Min number:", Min(numbers))
fmt.Println("Min word:", Min(words))
}
Output:
Add integers: 30
Add floats: 6
String representation: Alice (30 years old)
Min number: 1
Min word: apple
🔹 Generic Algorithms
Implement common algorithms using generics:
package main
import "fmt"
// Generic filter function
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, item := range slice {
if predicate(item) {
result = append(result, item)
}
}
return result
}
// Generic map function
func Map[T, U any](slice []T, transform func(T) U) []U {
result := make([]U, len(slice))
for i, item := range slice {
result[i] = transform(item)
}
return result
}
// Generic reduce function
func Reduce[T, U any](slice []T, initial U, reducer func(U, T) U) U {
result := initial
for _, item := range slice {
result = reducer(result, item)
}
return result
}
// Generic find function
func Find[T any](slice []T, predicate func(T) bool) (T, bool) {
for _, item := range slice {
if predicate(item) {
return item, true
}
}
var zero T
return zero, false
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// Filter even numbers
evens := Filter(numbers, func(n int) bool {
return n%2 == 0
})
fmt.Println("Even numbers:", evens)
// Map numbers to strings
strings := Map(numbers, func(n int) string {
return fmt.Sprintf("num_%d", n)
})
fmt.Println("Mapped strings:", strings[:3], "...")
// Reduce to sum
sum := Reduce(numbers, 0, func(acc, n int) int {
return acc + n
})
fmt.Println("Sum:", sum)
// Find first number > 5
found, exists := Find(numbers, func(n int) bool {
return n > 5
})
fmt.Printf("First number > 5: %d, Found: %t\n", found, exists)
}
Output:
Even numbers: [2 4 6 8 10]
Mapped strings: [num_1 num_2 num_3] ...
Sum: 55
First number > 5: 6, Found: true