๐ŸŽ“ A1Learner
๐Ÿ”
0 of 34 lessons completed

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?