Golang Generics For Non-Beginners
This shows the basics of Go Generics. Type parameters are constrained by interfaces. That means you can demand that your type parameter T
implements certain methods or are of certain types. Here we specify that the interface Number
matches anything that is a int
, int64
or a float64
.
type Number interface {
int | int64 | float64
}
func Sum[T Number](numbers []T) T {
var total T
for _, x := range numbers {
total += x
}
return total
}
Generic Types
It is important to note that when defining parameterized types, the methods we add cannot introduce new type parameters. They can only use the ones defined in the type definition. Hence, you will notice for Push
and Pop
that we don't specify constraints on T
In fact, there is nothing in the method definitions which hints at T
being a type parameter. Instead, Go derives that knowledge from the type definition. This is a restriction worth being aware of.
type Stack[T any] struct {
items []T
}
func (stack *Stack[T]) Push(value T) {
stack.items = append(stack.items, value)
}
func (stack *Stack[T]) Pop() {
n := len(stack.items)
if n <= 0 {
panic("Cannot pop an empty stack!")
}
stack.items = stack.items[:n-1]
}
Prepackaged Constraints
In our first example, we defined our own Number interface, but you don't have to do that. The golang.org/x/exp/constraints package gives a large collection of the most useful constrains. Here are some of the interfaces defined in the constraints package:
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
type Integer interface {
Signed | Unsigned
}
type Float interface {
~float32 | ~float64
}
type Ordered interface {
Integer | Float | ~string
}
Notice the tilde ~
usage. That is because Go lets you define new types easily, which are derived from primitive types. For instance, when writing code to simulate physics of a rocket launch I defined types for kilogram and Newton (force) like this:
type Newton float64
type Kg float64
If you had not used the tilde ~
for Float
then Newton
and Kg
would not have been considered Float
values which would have been impractical.
I used constraints from this package when defining nodes in a binary search tree.
import (
"fmt"
"golang.org/x/exp/constraints"
)
// A node in a binary tree which you can search by key
type TreeNode[K constraints.Ordered, V any] struct {
Key K
Value V
left, right *TreeNode[K, V]
}
// Give string representation of node in print statements
func (n *TreeNode[K, V]) String() string {
return fmt.Sprintf("TreeNode(%v, %v)", n.Key, n.Value)
}
// Insert node n into a leaf underneath parent node.
// Position will be determined based on value of key
func (parent *TreeNode[K, V]) Insert(n *TreeNode[K, V]) {
if n.Key >= parent.Key {
if parent.right == nil {
parent.right = n
} else {
parent.right.Insert(n)
}
} else {
if parent.left == nil {
parent.left = n
} else {
parent.left.Insert(n)
}
}
}
See Also
- Pipe output from one shell command to another using golang
- Golang mod import private package
- Golang upnp/ssdp DiscoverDevices example
- LRU Cache Implementation in Golang With container/list
- The way Convert an integer to a byte array with golang