Skip to main content

Go Ep 13: Generics (Type Parameters)

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
go - This article is part of a series.
Part 13: This Article
Before Go 1.18, writing a reusable function for finding a slice element required duplicate functions for int, string, and float64, or using reflection. Generics solve this using Type Parameters.

TL;DR (Quick Summary)
#

  • Type Parameters: Declared in square brackets: func Map[T, U any](s []T, f func(T) U) []U.
  • any: Alias for interface{}. Allows any type.
  • comparable: Built-in constraint allowing equality operations (==, !=). Required for map keys.
  • Underlying Type Approximation (~T): Matches custom types whose underlying type is T (e.g. type ID int matches ~int).

1. Generics Architecture
#


graph TD
    GenericFunc["'Generic Function: Map[T, U any"](s []T, f func(T) U) []U"]

    GenericFunc -->|Compile Time Monomorphization| IntVersion["'Instantiated: Map[int, string"]"]
    GenericFunc -->|Compile Time Monomorphization| FloatVersion["'Instantiated: Map[float64, int"]"]

2. Step-by-Step Lab: Generic Data Structures & Utilities
#

package main

import "fmt"

// Built-in constraint: 'comparable' allows == and != operations
func Contains[T comparable](slice []T, target T) bool {
 for _, item := range slice {
  if item == target {
   return true
  }
 }
 return false
}

// Custom Constraint Interface using Type Sets (|) and Approximation (~)
type Number interface {
 ~int | ~int64 | ~float64
}

func Sum[T Number](numbers []T) T {
 var total T
 for _, n := range numbers {
  total += n
 }
 return total
}

// Custom Type matching approximation ~int
type CustomID int

func main() {
 // 1. Generic Slice Contains check
 intSlice := []int{10, 20, 30}
 fmt.Println("Contains 20:", Contains(intSlice, 20)) // true

 strSlice := []string{"apple", "banana"}
 fmt.Println("Contains 'cherry':", Contains(strSlice, "cherry")) // false

 // 2. Generic Math Sum with underlying type approximation ~int
 customIDs := []CustomID{1, 2, 3}
 fmt.Println("Sum CustomIDs:", Sum(customIDs)) // 6

 floats := []float64{1.5, 2.5, 3.0}
 fmt.Println("Sum Floats:", Sum(floats)) // 7.0
}

3. Comparison: Interface vs Generics
#

FeatureInterface (any / interface{})Generics ([T any])
Type VerificationRuntime (Requires Type Assertions).Compile-Time (Strict type safety).
PerformanceMemory allocations on heap (eface boxing).Monomorphized code execution (No heap boxing).
Return TypeReturns any (Requires casting).Preserves exact caller type T.

4. Troubleshooting & Common Errors
#

Error 1: invalid operation: item == target (operator == not defined for T)
#

The Cause: Declaring a type parameter [T any] and attempting to use equality operators (==). any includes types that cannot be compared (like slices or maps). The Fix: Change the constraint from any to comparable: func Equal[T comparable](a, b T) bool.


Summary & Next Steps
#

In this episode:

  • We wrote generic functions using Type Parameters [T any].
  • We used comparable for map/slice equality operations.
  • We built custom constraint sets using type unions (|) and approximations (~).

In Episode 14: Testing, Benchmarking & Fuzzing, we will master testing.T, testing.B, and table-driven tests!

go - This article is part of a series.
Part 13: This Article