https://github.com/joeshaw/gengen
A Go source transformation tool for generics
https://github.com/joeshaw/gengen
ast generics golang
Last synced: 9 months ago
JSON representation
A Go source transformation tool for generics
- Host: GitHub
- URL: https://github.com/joeshaw/gengen
- Owner: joeshaw
- License: mit
- Archived: true
- Created: 2014-04-28T15:28:39.000Z (about 12 years ago)
- Default Branch: master
- Last Pushed: 2022-04-04T16:03:17.000Z (about 4 years ago)
- Last Synced: 2024-10-01T17:07:53.789Z (over 1 year ago)
- Topics: ast, generics, golang
- Language: Go
- Homepage:
- Size: 25.4 KB
- Stars: 262
- Watchers: 15
- Forks: 13
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# gengen - A generics code generator for Go #
🎉🎉 **Go 1.18 includes native support for generics!** 🎉🎉
---
People often lament the lack of generics in Go, and use it as an
[excuse to dismiss the
language](http://permalink.gmane.org/gmane.comp.lang.go.general/127789).
Yes, it is annoying that you often end up rewriting boilerplate. And
yes, it is annoying that it's not possible to write a generic data
structure that can be type-checked at compile time.
However, we can use Go's [powerful source parsing and AST
representation packages](http://golang.org/pkg/go/) to build a program
that can translate generically-defined code into specifically typed
source code and compile that into our projects.
## How to use it ##
Install the `gengen` tool:
$ go install github.com/joeshaw/gengen@latest
Create a Go package with a generic implementation. For example, this
contrived linked-list implementation in `list.go`, which lives in the
`github.com/joeshaw/gengen/examples/list` package:
```go
package list
import "github.com/joeshaw/gengen/generic"
type List struct {
data generic.T
next *List
}
func (l *List) Prepend(d generic.T) *List {
n := &List{
data: d,
next: l,
}
return n
}
func (l *List) Contains(d generic.T) bool {
if l == nil {
return false
}
for i := l; i != nil; i = i.next {
if i.data == d {
return true
}
}
return false
}
func (l *List) Data() generic.T {
if l == nil {
// Return the zero value for generic.T, whatever type it ends
// up becoming
var zero generic.T
return zero
}
return l.data
}
```
`generic.T` is simply `interface{}`. This list implementation is
perfectly valid Go code and you could use it as-is, asserting types
at runtime.
However, you can generate a specifically typed version of this file by
running it through `gengen`:
$ gengen github.com/joeshaw/gengen/examples/list string
This will generate a `list.go` that looks like this:
```go
package list
type List struct {
data string
next *List
}
func (l *List) Prepend(d string) *List {
n := &List{
data: d,
next: l,
}
return n
}
func (l *List) Contains(d string) bool {
if l == nil {
return false
}
for i := l; i != nil; i = i.next {
if i.data == d {
return true
}
}
return false
}
func (l *List) Data() string {
if l == nil {
// Return the zero value for generic.T, whatever type it ends
// up becoming (in this example, string)
var zero string
return zero
}
return l.data
}
```
The `generic` package also defines `generic.U` and `generic.V` as
additional generic types for cases when you want to support more than
one type. Simply pass the additional types on the `gengen` command
line:
$ gengen github.com/joeshaw/gengen/examples/btree int string
Lastly, you can use `gengen` in conjunction with `go generate`. For
example:
//go:generate gengen -o ./btree github.com/joeshaw/gengen/examples/btree string int
## Caveats ##
### Number of generic types ###
Currently `gengen` can support up to three generic types: `generic.T`,
`generic.U`, and `generic.V`.
### Package Naming ###
`gengen` does not currently do anything with naming of packages or
types. If you want to import multiple copies of a package (either
generic or typed) you will need to rename the package at import time.
For example, after generating a typed btree into
`github.com/example/btree`:
import "github.com/example/btree"
import gen_btree "github.com/joeshaw/gengen/examples/btree"
### Using zero values ###
You may need to write code in a slightly different way than you
normally would for `interface{}` in order to support a wide range of
types. For instance, in our `Data()` method, note that we cannot
simply `return nil` in the `l == nil` case because `nil` is not a
valid value for primitive types like `int`, `string`, etc. Instead we
instantiate a variable of our generic type but do not assign to it,
ensuring that we always return the zero value for that type.
### Equality ###
Checking for equality in a generic implementation can be tricky, and
blindly checking `if x == y` [often will not work as you'd
hope](http://golang.org/ref/spec#Comparison_operators). For things
like slices, it will not even compile. If you need to check for
equality, you might want to create an `Equaler` interface, like so:
```go
type Equaler interface {
Equal(other Equaler) bool
}
```
Define types that implement this interface:
```go
type intWithEqual int
func (i intWithEqual) Equal(other Equaler) bool {
if i2, ok := other.(intWithEqual); ok {
return i == i2
}
return false
}
```
```go
type Person struct {
Name string
SSN string
}
func (p *Person) Equal(other Equaler) bool {
if p2, ok := other.(*Person); ok {
return p.SSN == p2.SSN
}
return false
}
```
In your generic implementation, use the interface rather than
comparing directly:
```go
type MySlice []generic.T
func (s MySlice) Contains(e generic.T) bool {
for _, e2 := range s {
if e2.Equal(e) {
return true
}
}
return false
}
```
(Note that because `generic.T` does not embed the `Equaler` interface,
this code won't compile without being run through `gengen` first.)
Finally, generate your implementations:
$ gengen myslice.go intWithEqual > myslice_int.go
$ gengen myslice.go *Person > myslice_person.go
### Import and type naming inflexibility ###
The `gengen` tool looks through the source code for specific strings
in order to replace them in the AST. Specifically, it looks for the
import `github.com/joeshaw/gengen/generic` and the types `generic.T`,
`generic.U`, and `generic.V`. If you need to change these, you will
also have to change the `gengen.go` source.
## Origins ##
I had been mulling the idea of a generics generator for a while,
originally planning to use the `text/template` package. However,
during a [panel discussion at
GopherCon](http://gophercon.sourcegraph.com/post/83845316771/panel-discussion-with-go-team-members)
in which generics inevitably came up, Rob Pike suggested manipulating
the AST for Go. I began implementing this approach during the
GopherCon Hack Day on 26 April 2014.