Encoding Sum Types in Go Using Constraints

Encoding Sum Types in Go Using Constraints

Go 1.18 was released last month to rowdy applause. It was the big release we've been waiting for. It brought some exciting features such as fuzzing and generics, as well as improvements to parts of the standard library. On the whole, the Go team has once again delivered on their promise of not breaking our Go programs. Think about it: they added generics to the language while maintaining backwards compatibility!

Generics is probably the most exciting part of the 1.18 release, for good reason. Of course it currently has some limitations, but those rough edges will be smoothed over time. I've been waiting to try out one feature since I read the Type Parameters Proposalconstraints as ordinary interface types.

Here's the key excerpt from the proposal:

This would permit a version of what other languages call sum types or union types. It would be a Go interface type to which only specific types could be assigned. Such an interface type could still take the value nil, of course, so it would not be quite the same as a typical sum type as found in other languages.

The cool thing about this feature is that it allows us have sum types in Go, with the caveat that nil is part of every sum type. If constraints are ordinary interfaces, we can assign any value whose type satisfies the constraint to a variable whose type is the constraint. That means we can write programs that accept constraints and return constraints. For example, we could improve error handling by having a standard Result type like that in Rust:

type Ok[T any] struct {
    Value T
}

type Error[E any] struct {
    Value E
}

type Result[T, E any] interface {
    Ok[T] | Error[E]
}

The Result interface is a constraint to which we can assign either Ok values or Error values, and we'll know the precise types of the result or the error at compile time. Goodbye to parsing errors out of error.Error().

However, I noticed a caveat on my first try. I must have missed it during my first reading of the proposal, or the proposal may have been edited. Here's a screenshot of the full paragraph on permitting constraints as ordinary interface types. permitting-constraints-as-ordinary-interface-types.png

That's a downer, right? Well, all hope is not lost. Let's look at an alternative solution. This solution is more enlightening, although direct support for constraints as ordinary interface types would be desirable.

encoding-sum-types-in-Go.jpeg

You see, it's that simple. Just use Haskell.