What is a sum of types and how to implement it in Go?
sobes.tech AI
Answer from AI
Sum types (or tagged unions, disjoint unions) are data types that can hold a value of one of several predefined options. Unlike product types (structures), where the value contains all fields simultaneously, sum types contain only one of the possible values.
In Go, there is no direct support for sum types like in languages such as Haskell or Rust (enums). Sum types can be emulated in several ways:
-
Interfaces and type assertion:
Create an interface that all possible options will implement. The value of the sum type will be represented by this interface, and a type assertion or
type switchis used to determine the specific variant.package main import "fmt" // Shape - interface representing the sum type type Shape interface { Area() float64 } // Circle - one variant type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius } // Rectangle - another variant type Rectangle struct { Width, Height float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func main() { shapes := []Shape{Circle{Radius: 5}, Rectangle{Width: 3, Height: 4}} for _, s := range shapes { // Using type switch to determine the variant switch v := s.(type) { case Circle: fmt.Printf("Circle with radius %.2f, area: %.2f\n", v.Radius, v.Area()) case Rectangle: fmt.Printf("Rectangle with dimensions %.2f x %.2f, area: %.2f\n", v.Width, v.Height, v.Area()) default: fmt.Println("Unknown shape") } } } -
Structures with boolean flags (rarely used):
A structure contains fields for all possible options and boolean flags to indicate which option is active. This approach is unsafe and hard to maintain.
package main import "fmt" type Result struct { Value int // field for successful result Err error // field for error IsValue bool // flag indicating if Value is active IsErr bool // flag indicating if Err is active } func Process(input int) Result { if input > 0 { return Result{Value: input * 2, IsValue: true} } return Result{Err: fmt.Errorf("negative number: %d", input), IsErr: true} } func main() { res1 := Process(10) if res1.IsValue { fmt.Printf("Result: %d\n", res1.Value) } else if res1.IsErr { fmt.Printf("Error: %v\n", res1.Err) } res2 := Process(-5) if res2.IsValue { fmt.Printf("Result: %d\n", res2.Value) } else if res2.IsErr { fmt.Printf("Error: %v\n", res2.Err) } } -
Structures with zero value for inactive fields (often used for Optional/Result):
A structure contains fields for all options, but only one will be non-zero. Works well with pointers or types with a clear "zero" value.
package main import "fmt" // Option - emulation of Some/None from optional type type Option struct { Value *int // non-nil if there is a value } // None creates a zero value Option func None() Option { return Option{} } // Some creates an Option with a value func Some(val int) Option { return Option{Value: &val} } func main() { opt1 := Some(10) if opt1.Value != nil { fmt.Printf("Value exists: %d\n", *opt1.Value) } else { fmt.Println("No value") } opt2 := None() if opt2.Value != nil { fmt.Printf("Value exists: %d\n", *opt2.Value) } else { fmt.Println("No value") } }
The most idiomatic and safe way to emulate sum types in Go is using interfaces and type switch. This guarantees that only one variant is present and provides type safety when working with it.