Golang Generics For Non-Beginners
GolangWebDev
GolangWebDev
505 0 0

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)
		}
	}
}
0

See Also


Discussion

Login Topics