Skip to content
widmogrodPublic

Latest commit

 

History

1,358 Commits

Folders and files

Repository files navigation

Welcome to MkUnion

Go Reference Go Report Card codecov

Add mkunion as a tool dependency of your module (needs Go 1.24 or newer):

go get -tool github.com/widmogrod/mkunion/cmd/mkunion@v1.26.1
go tool mkunion watch -g ./...

This writes a tool line into your go.mod, so the version is pinned together with the rest of your dependencies. Everyone on your team and your CI then run the same mkunion, and generated files stay stable. No go install, no PATH changes.

On Go older than 1.24, run go install github.com/widmogrod/mkunion/cmd/mkunion@v1.26.1 instead and drop the go tool prefix from the commands.

About

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

Why

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.

Examples

Example 1: Union definition and pattern matching with JSON marshaling

//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 },
)

Example 2: Result Type for Error Handling

//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.

Example 3: AST and Recursive Types

//go:tag mkunion:"Calc"
type (
    Lit struct{ V int }
    Sum struct{ Left, Right Calc }
    Mul struct{ Left, Right Calc }
)

Example 4: States for State Machines or Events for Event Sourcing

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

Example 5: HTTP API responses

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

Example 6: Configuration type

//go:tag mkunion:"Config"  
type (
    FileConfig struct{ Path string }
    EnvConfig struct{ Prefix string }
    DefaultConfig struct{}
)

Next

Releases

Packages

Used by

Contributors

Languages