Phantom Types
Leverage Go's type system for compile-time guarantees.
Measurements as phantom types
Phantom types for units COULD help prevents catastrophic bugs like the Mars Climate Orbiter ($327M loss due to metric/imperial confusion). The type system won't let you mix incompatible units.
example/units.go
//
//go:tag mkunion:"Measurement[Unit]"
type (
Distance[Unit any] struct{ value float64 }
Speed[Unit any] struct{ value float64 }
)
//go:tag mkunion:"Time[Unit]"
type (
AnyTime[Unit any] struct{ value float64 }
PositiveTime[Unit any] struct{ value float64 }
)
type Meters struct{}
type Feet struct{}
type Seconds struct{}
type Hours struct{}
type MetersPerSecond struct{}
type MilesPerHour struct{}
func NewDistance(value float64) *Distance[Meters] {
return &Distance[Meters]{value: value}
}
// ToFeet Type-safe unit conversions
func (d *Distance[Meters]) ToFeet() *Distance[Feet] {
return &Distance[Feet]{value: d.value * 3.28084}
}
func (t *PositiveTime[Seconds]) ToHours() *PositiveTime[Hours] {
return &PositiveTime[Hours]{value: t.value / 3600}
}
func NewTime(value float64) Time[Seconds] {
if value <= 0 {
return &AnyTime[Seconds]{value: value}
}
return &PositiveTime[Seconds]{value: value}
}
// CalculateSpeed only compatible units can be combined
func CalculateSpeed(distance *Distance[Meters], time *PositiveTime[Seconds]) *Speed[MetersPerSecond] {
return &Speed[MetersPerSecond]{value: distance.value / time.value}
}
State tracking and phantom types
example/connection.go
//go:tag mkunion:"Connection[State]"
type (
Disconnected[State any] struct{}
Connecting[State any] struct{ Addr string }
Connected[State any] struct{ Conn net.Conn }
)
// Type-safe state machine with phantom types
type Unopened struct{}
type Open struct{}
type Closed struct{}
// Only allow certain operations in specific states
func (c *Connected[Open]) Send(data []byte) error {
// Can only send on open connections
_, err := c.Conn.Write(data)
return err
}
func (c *Connected[Open]) Close() Connection[Closed] {
c.Conn.Close()
return &Disconnected[Closed]{}
}
// Compile error: cannot call Send on closed connection!
// func (c *Connected[Closed]) Send(data []byte) error { ... }