
Generics in Go: A Simple Guide with Examples
Generics in Go allow you to write flexible, reusable code that works with different types while ensuring type safety. They were introduced in Go 1.18 and are especially helpful for functions, structs, or methods that need to operate on multiple data types without duplicating code.
1. What are Generics?
2. Basic Syntax
func FunctionName[T any](param T) T {
// your logic here
return param
}
T
is a type parameter. You can name it anything, butT
is common.any
is a built-in type constraint that means “any type.”
3. Generic Functions
Example: A function to get the first item of any slice
package main
import "fmt"
// Generic function
func First[T any](items []T) T {
return items[0]
}
func main() {
nums := []int{1, 2, 3}
words := []string{"hello", "world"}
fmt.Println(First(nums)) // Output: 1
fmt.Println(First(words)) // Output: hello
}
Explanation:
First[T any]
: The function accepts a typeT
.items []T
: The parameter is a slice of any typeT
.- The function returns the first element of the slice, regardless of its type.
4. Generic Structs
package main
import "fmt"
// Generic struct
type Pair[T any] struct {
First T
Second T
}
func main() {
intPair := Pair[int]{First: 1, Second: 2}
stringPair := Pair[string]{First: "hello", Second: "world"}
fmt.Println(intPair) // Output: {1 2}
fmt.Println(stringPair) // Output: {hello world}
}
Explanation:
Pair[T any]
: The struct accepts a typeT
.- You can create pairs of integers, strings, or any other type.
5. Constraints
Example: A function to sum a slice of numbers
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// Generic function with constraints
func Sum[T constraints.Ordered](numbers []T) T {
var total T
for _, n := range numbers {
total += n
}
return total
}
func main() {
ints := []int{1, 2, 3, 4}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(ints)) // Output: 10
fmt.Println(Sum(floats)) // Output: 6.6
}
Explanation:
constraints.Ordered
ensures the types are comparable (e.g., integers, floats, or strings).- The
Sum
function works for both integers and floats.
Using a Number
Type Constraint
To accept only numeric types (like integers and floats), you can define a custom constraint called Number
.
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// Define a custom Number type constraint
type Number interface {
constraints.Integer | constraints.Float
}
// Generic function using the Number constraint
func Add[T Number](a, b T) T {
return a + b
}
func main() {
fmt.Println(Add(5, 10)) // Works with integers, Output: 15
fmt.Println(Add(3.2, 4.8)) // Works with floats, Output: 8
// fmt.Println(Add(5, "hello")) // Compilation error: string not allowed
}
Without constraints
(Manual Custom Type Constraint)
package main
import "fmt"
// Define the Number constraint manually
type Number interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
// Generic function using the Number constraint
func Multiply[T Number](a, b T) T {
return a * b
}
func main() {
fmt.Println(Multiply(4, 5)) // Output: 20 (int)
fmt.Println(Multiply(2.5, 3.0)) // Output: 7.5 (float64)
// fmt.Println(Multiply("a", "b")) // Compilation error: string not allowed
}
Explanation:
Number
is an interface that lists all numeric types you want to allow.- The function ensures only types satisfying the
Number
constraint can be used. - Compile-time errors prevent misuse.
6. Multiple Type Parameters
package main
import "fmt"
// Generic function with two type parameters
func Swap[T, U any](a T, b U) (U, T) {
return b, a
}
func main() {
x, y := 1, "hello"
a, b := Swap(x, y)
fmt.Println(a, b) // Output: hello 1
}
7. Best Practices
Generics in Go make your code cleaner and reduce repetition while maintaining Go’s simplicity and performance. Experiment with these examples to get comfortable!