Go doesn’t have unions, but there’s a hack to do it.

Assume we want to create a union type of a node that may be a text node or a container node that holds text nodes. The following code (playground) demonstrates this:

package main

type node interface {
	node()
}

type textNode struct {
	text string
}

func (textNode) node() {}

type containerNode []node

func (containerNode) node() {}

func main() {
	nodes := []node{
		textNode{"hello"},
		textNode{" "},
		containerNode{
			textNode{"world"},
			textNode{"!"},
		},
	}

	printNodes(nodes)
}

func printNodes(nodes []node) {
	for _, n := range nodes {
		switch n := n.(type) {
		case textNode:
			fmt.Print(n.text)
		case containerNode:
			printNodes([]node(n))
		}
	}
}

This trick does in fact give us some type checking during compile time: the following code

switch node := node.(type) {
case string:
}

will error out when built:

impossible type switch case: n (type node) cannot have dynamic type string (missing node method)