go install github.com/widmogrod/mkunion/cmd/mkunion@v1.26.1mkunion watch -g ./...
Strongly typed union type in golang that supports generics*.
- with exhaustive pattern matching support
- with json marshalling including generics
- and as a bonus, can generate compatible TypeScript types for end-to-end type safety in your application
Historically, in languages like Go that lack native union types, developers have resorted to workarounds such as the Visitor pattern or iota with switch statements.
The Visitor pattern requires a lot of boilerplate code and manual crafting of the Accept method for each type in the union.
Using iota and switch statements is not type-safe and can lead to runtime errors, especially when a new type is added and not all case statements are updated.
On top of that, any data marshalling, like to/from JSON, requires additional, handcrafted code to make it work.
MkUnion solves all of these problems by generating opinionated and strongly typed, meaningful code for you.
//go:tag mkunion:"Shape"
type (
Circle struct{ Radius float64 }
Rectangle struct{ Width, Height float64 }
Triangle struct{ Base, Height float64 }
)
// Generated code provides:
area := MatchShapeR1(
shape,
func(c *Circle) float64 { return math.Pi * c.Radius * c.Radius },
func(r *Rectangle) float64 { return r.Width * r.Height },
func(t *Triangle) float64 { return 0.5 * t.Base * t.Height },
)//go:tag mkunion:"Option[T]"
type (
None[T any] struct{}
Some[T any] struct{ Value T }
)
//go:tag mkunion:"Result[T, E]"
type (
Ok[T, E any] struct{ Value T }
Err[T, E any] struct{ Error E }
)
type User struct{ Name string }
type APIError struct {
Code int
Message string
}
// FetchResult combine unions for rich error handling
type FetchResult = Result[Option[User], APIError]
// handleFetch uses nested pattern matching to handle result
func handleFetch(result FetchResult) string {
return MatchResultR1(result,
func(ok *Ok[Option[User], APIError]) string {
return MatchOptionR1(ok.Value,
func(*None[User]) string { return "User not found" },
func(some *Some[User]) string {
return fmt.Sprintf("Found user: %s", some.Value.Name)
},
)
},
func(err *Err[Option[User], APIError]) string {
return fmt.Sprintf("API error: %v", err.Error)
},
)
}Important: Generic unions MUST specify their type parameters in the tag. The type parameter names in the tag must match those used in the variant types.
//go:tag mkunion:"Calc"
type (
Lit struct{ V int }
Sum struct{ Left, Right Calc }
Mul struct{ Left, Right Calc }
)//go:tag mkunion:"OrderState"
type (
Draft struct{ Items []Item }
Submitted struct{ OrderID string; Items []Item }
Shipped struct{ OrderID string; TrackingNumber string }
Delivered struct{ OrderID string; DeliveredAt time.Time }
)//go:tag mkunion:"APIResponse[T]"
type (
Success[T any] struct{ Data T; Status int }
ValidationError[T any] struct{ Errors []string }
ServerError[T any] struct{ Message string; Code string }
)//go:tag mkunion:"Config"
type (
FileConfig struct{ Path string }
EnvConfig struct{ Prefix string }
DefaultConfig struct{}
)- Read getting started to learn more.
- Learn more about value proposition.