Go Constraints
A constraint is a rule that limits the types a placeholder can take. A function can then use only the operations the rule allows.
A named constraint for ordered types
The cmp package gives the Ordered constraint. Ordered covers numbers and strings, which all have a natural order.
package main
import (
"cmp"
"fmt"
)
func largest[T cmp.Ordered](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([]string{"fig", "plum", "kiwi"}))
}A custom constraint with a union
The Number constraint lists int and float64. A named type built on int has int as its underlying type, and the tilde lets such a type in.
package main
import "fmt"
type Number interface {
~int | ~float64
}
type Score int
func sum[T Number](values []T) T {
var total T
for _, value := range values {
total += value
}
return total
}
func main() {
fmt.Println(sum([]int{1, 2, 3}))
fmt.Println(sum([]float64{1.5, 2.5}))
fmt.Println(sum([]Score{10, 20}))
}comparable allows equality checks
The comparable constraint covers types that support the == sign. The search compares each value with the target.
package main
import "fmt"
func contains[T comparable](values []T, target T) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}
func main() {
fmt.Println(contains([]int{2, 4, 6}, 4))
fmt.Println(contains([]string{"fig", "plum"}, "kiwi"))
}A string fails the union
This program passes strings to a function limited to int and float64. The compiler refuses the program.
package main
import "fmt"
func largest[T int | float64](values []T) T {
best := values[0]
for _, value := range values {
if value > best {
best = value
}
}
return best
}
func main() {
fmt.Println(largest([]string{"fig", "plum"}))
}Example
package main
import "fmt"
type Number interface {
~int | ~float64
}
func sum[T Number](values []T) T {
var total T
for _, value := range values {
total += value
}
return total
}
func largest[T Number](values []T) T {
best := values[0]
for _, value := range values {
if value > best {
best = value
}
}
return best
}
func main() {
nums := []int{3, 9, 4}
prices := []float64{1.5, 7.25, 2.5}
fmt.Println(sum(nums))
fmt.Println(largest(nums))
fmt.Println(sum(prices))
fmt.Println(largest(prices))
}Key points
- A constraint names the set of types a placeholder can take.
- cmp.Ordered covers types that have a natural order.
- A constraint can be an interface that lists a union of types.
- The tilde sign ~ also lets in named types built on a listed type.
- comparable covers types that can be checked with ==.
In this example, the Number constraint lists int and float64 with a tilde on each. The sum function adds a slice of either type, and largest finds the biggest value in either type. The nums slice prints a sum of 16 and a largest of 9. The prices slice prints a sum of 11.25 and a largest of 7.25.
Note
A tighter constraint allows more operations inside the function.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Go Playground
Exercise
Which constraint allows a generic function to compare values with ==?