Skip to content

Instantly share code, notes, and snippets.

@DylanSp
Created July 4, 2022 23:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DylanSp/89d1cf1eb7b58c17626db55e9756c1a2 to your computer and use it in GitHub Desktop.
Save DylanSp/89d1cf1eb7b58c17626db55e9756c1a2 to your computer and use it in GitHub Desktop.
Phantom types in Go
package main
import (
"fmt"
"github.com/google/uuid"
)
// with this, the "value" field can't be accessed outside the package with PhantomID,
// which prevents PhantomID[T] from being compared to PhantomID[U]
// makes working with the ID any other way difficult, however
type PhantomID[T any] struct {
value uuid.UUID
}
type Dog1 struct {
id PhantomID[Dog1]
breed string
}
type Cat1 struct {
id PhantomID[Cat1]
numToes int
}
// embedding allows any caller to access the underlying UUID with id.UUID;
// which might allow incorrect ID comparisons, but allows the UUID to be read for other uses
type EmbeddedPhantomID[T any] struct {
uuid.UUID
}
type Dog2 struct {
id EmbeddedPhantomID[Dog2]
breed string
}
type Cat2 struct {
id EmbeddedPhantomID[Cat2]
numToes int
}
func main() {
dog1 := Dog1{
// have to specify type parameter when instantiating PhantomID
id: PhantomID[Dog1]{
value: uuid.New(),
},
breed: "Beagle",
}
cat1 := Cat1{
// have to specify type parameter when instantiating PhantomID
id: PhantomID[Cat1]{
value: uuid.New(),
},
numToes: 6,
}
// compiles; isn't rejected by typechecker
fmt.Println(dog1.id.value == cat1.id.value)
// is correctly rejected by typechecker; does not compile
// fmt.Println(dog1.id == cat1.id)
// compiles (correctly), returns true
fmt.Println(dog1.id == dog1.id)
dog2 := Dog2{
// have to specify type parameter when instantiating EmbeddedPhantomID
id: EmbeddedPhantomID[Dog2]{uuid.New()},
breed: "Golden Shepherd",
}
cat2 := Cat2{
// have to specify type parameter when instantiating EmbeddedPhantomID
id: EmbeddedPhantomID[Cat2]{uuid.New()},
numToes: 5,
}
// compiles; isn't rejected by typechecker
fmt.Println(dog2.id.UUID == cat2.id.UUID)
// is correctly rejected by typechecker; does not compile
// fmt.Println(dog2.id == cat2.id)
// compiles (correctly)
fmt.Println(dog2.id == dog2.id)
}
@DylanSp
Copy link
Author

DylanSp commented Jul 4, 2022

Future work:

  • Generic type for adding ID to an existing type
  • For "private" value, exposing functions to get/set it for use when it's really needed (i.e. reading value from database)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment