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

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 ==?