{"id":15294888,"url":"https://github.com/joeshaw/gengen","last_synced_at":"2025-10-07T06:31:25.238Z","repository":{"id":16490994,"uuid":"19243614","full_name":"joeshaw/gengen","owner":"joeshaw","description":"A Go source transformation tool for generics","archived":true,"fork":false,"pushed_at":"2022-04-04T16:03:17.000Z","size":26,"stargazers_count":262,"open_issues_count":4,"forks_count":13,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-10-01T17:07:53.789Z","etag":null,"topics":["ast","generics","golang"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joeshaw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-04-28T15:28:39.000Z","updated_at":"2024-06-06T15:29:28.000Z","dependencies_parsed_at":"2022-08-29T20:31:33.662Z","dependency_job_id":null,"html_url":"https://github.com/joeshaw/gengen","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joeshaw%2Fgengen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joeshaw%2Fgengen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joeshaw%2Fgengen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joeshaw%2Fgengen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joeshaw","download_url":"https://codeload.github.com/joeshaw/gengen/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235599953,"owners_count":19016191,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ast","generics","golang"],"created_at":"2024-09-30T17:07:47.186Z","updated_at":"2025-10-07T06:31:19.898Z","avatar_url":"https://github.com/joeshaw.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gengen - A generics code generator for Go #\n\n🎉🎉 **Go 1.18 includes native support for generics!** 🎉🎉\n\n---\n\nPeople often lament the lack of generics in Go, and use it as an\n[excuse to dismiss the\nlanguage](http://permalink.gmane.org/gmane.comp.lang.go.general/127789).\nYes, it is annoying that you often end up rewriting boilerplate.  And\nyes, it is annoying that it's not possible to write a generic data\nstructure that can be type-checked at compile time.\n\nHowever, we can use Go's [powerful source parsing and AST\nrepresentation packages](http://golang.org/pkg/go/) to build a program\nthat can translate generically-defined code into specifically typed\nsource code and compile that into our projects.\n\n## How to use it ##\n\nInstall the `gengen` tool:\n\n    $ go install github.com/joeshaw/gengen@latest\n\nCreate a Go package with a generic implementation.  For example, this\ncontrived linked-list implementation in `list.go`, which lives in the\n`github.com/joeshaw/gengen/examples/list` package:\n\n```go\npackage list\n\nimport \"github.com/joeshaw/gengen/generic\"\n\ntype List struct {\n    data generic.T\n    next *List\n}\n\nfunc (l *List) Prepend(d generic.T) *List {\n    n := \u0026List{\n        data: d,\n        next: l,\n    }\n\n    return n\n}\n\nfunc (l *List) Contains(d generic.T) bool {\n    if l == nil {\n        return false\n    }\n\n    for i := l; i != nil; i = i.next {\n        if i.data == d {\n            return true\n        }\n    }\n\n    return false\n}\n\nfunc (l *List) Data() generic.T {\n    if l == nil {\n        // Return the zero value for generic.T, whatever type it ends\n        // up becoming\n        var zero generic.T\n        return zero\n    }\n\n    return l.data\n}\n\n```\n\n`generic.T` is simply `interface{}`.  This list implementation is\nperfectly valid Go code and you could use it as-is, asserting types\nat runtime.\n\nHowever, you can generate a specifically typed version of this file by\nrunning it through `gengen`:\n\n    $ gengen github.com/joeshaw/gengen/examples/list string\n\nThis will generate a `list.go` that looks like this:\n\n```go\npackage list\n\ntype List struct {\n    data string\n    next *List\n}\n\nfunc (l *List) Prepend(d string) *List {\n    n := \u0026List{\n        data: d,\n        next: l,\n    }\n\n    return n\n}\n\nfunc (l *List) Contains(d string) bool {\n    if l == nil {\n        return false\n    }\n\n    for i := l; i != nil; i = i.next {\n        if i.data == d {\n            return true\n        }\n    }\n\n    return false\n}\n\nfunc (l *List) Data() string {\n    if l == nil {\n        // Return the zero value for generic.T, whatever type it ends\n        // up becoming (in this example, string)\n        var zero string\n        return zero\n    }\n\n    return l.data\n}\n\n```\n\nThe `generic` package also defines `generic.U` and `generic.V` as\nadditional generic types for cases when you want to support more than\none type.  Simply pass the additional types on the `gengen` command\nline:\n\n    $ gengen github.com/joeshaw/gengen/examples/btree int string\n\nLastly, you can use `gengen` in conjunction with `go generate`.  For\nexample:\n\n    //go:generate gengen -o ./btree github.com/joeshaw/gengen/examples/btree string int\n\n## Caveats ##\n\n### Number of generic types ###\n\nCurrently `gengen` can support up to three generic types: `generic.T`,\n`generic.U`, and `generic.V`.\n\n### Package Naming ###\n\n`gengen` does not currently do anything with naming of packages or\ntypes.  If you want to import multiple copies of a package (either\ngeneric or typed) you will need to rename the package at import time.\nFor example, after generating a typed btree into\n`github.com/example/btree`:\n\n    import \"github.com/example/btree\"\n    import gen_btree \"github.com/joeshaw/gengen/examples/btree\"\n\n### Using zero values ###\n\nYou may need to write code in a slightly different way than you\nnormally would for `interface{}` in order to support a wide range of\ntypes.  For instance, in our `Data()` method, note that we cannot\nsimply `return nil` in the `l == nil` case because `nil` is not a\nvalid value for primitive types like `int`, `string`, etc.  Instead we\ninstantiate a variable of our generic type but do not assign to it,\nensuring that we always return the zero value for that type.\n\n### Equality ###\n\nChecking for equality in a generic implementation can be tricky, and\nblindly checking `if x == y` [often will not work as you'd\nhope](http://golang.org/ref/spec#Comparison_operators).  For things\nlike slices, it will not even compile.  If you need to check for\nequality, you might want to create an `Equaler` interface, like so:\n\n```go\ntype Equaler interface {\n    Equal(other Equaler) bool\n}\n```\n\nDefine types that implement this interface:\n\n\n```go\ntype intWithEqual int\n\nfunc (i intWithEqual) Equal(other Equaler) bool {\n    if i2, ok := other.(intWithEqual); ok {\n        return i == i2\n    }\n    return false\n}\n```\n\n```go\ntype Person struct {\n    Name string\n    SSN string\n}\n\nfunc (p *Person) Equal(other Equaler) bool {\n    if p2, ok := other.(*Person); ok {\n        return p.SSN == p2.SSN\n    }\n    return false\n}\n```\n\nIn your generic implementation, use the interface rather than\ncomparing directly:\n\n```go\ntype MySlice []generic.T\n\nfunc (s MySlice) Contains(e generic.T) bool {\n    for _, e2 := range s {\n        if e2.Equal(e) {\n            return true\n        }\n    }\n    return false\n}\n```\n\n(Note that because `generic.T` does not embed the `Equaler` interface,\nthis code won't compile without being run through `gengen` first.)\n\nFinally, generate your implementations:\n\n    $ gengen myslice.go intWithEqual \u003e myslice_int.go\n    $ gengen myslice.go *Person \u003e myslice_person.go\n\n### Import and type naming inflexibility ###\n\nThe `gengen` tool looks through the source code for specific strings\nin order to replace them in the AST.  Specifically, it looks for the\nimport `github.com/joeshaw/gengen/generic` and the types `generic.T`,\n`generic.U`, and `generic.V`.  If you need to change these, you will\nalso have to change the `gengen.go` source.\n\n## Origins ##\n\nI had been mulling the idea of a generics generator for a while,\noriginally planning to use the `text/template` package.  However,\nduring a [panel discussion at\nGopherCon](http://gophercon.sourcegraph.com/post/83845316771/panel-discussion-with-go-team-members)\nin which generics inevitably came up, Rob Pike suggested manipulating\nthe AST for Go.  I began implementing this approach during the\nGopherCon Hack Day on 26 April 2014.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoeshaw%2Fgengen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoeshaw%2Fgengen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoeshaw%2Fgengen/lists"}