Go Generic Functions
A generic function is a function written for a placeholder type. One definition can work with many types.
Two copies do the same job
Two functions find the largest value. The bodies match apart from the type.
package main
import "fmt"
func largestInt(values []int) int {
best := values[0]
for _, value := range values {
if value > best {
best = value
}
}
return best
}
func largestString(values []string) string {
best := values[0]
for _, value := range values {
if value > best {
best = value
}
}
return best
}
func main() {
fmt.Println(largestInt([]int{3, 9, 4}))
fmt.Println(largestString([]string{"fig", "plum", "kiwi"}))
}One generic function replaces both
A union sits inside the brackets. The placeholder can take an int, a float64, or a string.
package main
import "fmt"
func largest[T int | float64 | string](values []T) T {
best := values[0]
for _, value := range values {
if value > best {
best = value
}
}
return best
}
func main() {
fmt.Println(largest([]int{3, 9, 4}))
fmt.Println(largest([]float64{2.5, 7.25, 1.5}))
fmt.Println(largest([]string{"fig", "plum", "kiwi"}))
}A generic reverse for two types
The word any lets the placeholder take every type. Reverse builds a new slice in reverse order.
package main
import "fmt"
func reverse[T any](values []T) []T {
out := make([]T, len(values))
for i, value := range values {
out[len(values)-1-i] = value
}
return out
}
func main() {
fmt.Println(reverse([]int{1, 2, 3}))
fmt.Println(reverse([]string{"a", "b", "c"}))
}A generic helper reads map keys
The helper takes a map with string keys. It sorts the keys before it returns them.
package main
import (
"fmt"
"sort"
)
func sortedKeys[V any](values map[string]V) []string {
keys := make([]string, 0, len(values))
for key := range values {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
func main() {
counts := map[string]int{"plum": 3, "fig": 1, "kiwi": 2}
fmt.Println(sortedKeys(counts))
}Example
package main
import "fmt"
func largest[T int | float64 | string](values []T) T {
best := values[0]
for _, value := range values {
if value > best {
best = value
}
}
return best
}
func reverse[T any](values []T) []T {
out := make([]T, len(values))
for i, value := range values {
out[len(values)-1-i] = value
}
return out
}
func main() {
nums := []int{4, 8, 2}
words := []string{"fig", "plum", "kiwi"}
prices := []float64{1.5, 7.5, 3.25}
fmt.Println(largest(nums))
fmt.Println(reverse(nums))
fmt.Println(largest(words))
fmt.Println(reverse(words))
fmt.Println(largest(prices))
}Key points
- A placeholder is a name that stands in for a real type.
- Type parameters sit in square brackets after the function name.
- A union lists the types a placeholder can take, with a bar sign between them.
- Use any when a placeholder can take every type.
- Go fills in the real type at each call from the values passed in.
In this example, the program defines a generic largest and a generic reverse. The nums slice holds ints, and largest prints 8 while reverse prints the slice backwards. The words slice holds strings, and the same two functions print plum and the reversed words. The prices slice holds float64 values, and largest prints 7.5.
Note
One generic definition replaces a set of near copies.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Go Playground
Exercise
Which line declares a function with a placeholder type?