Generic, a code generation tool to enable generics in go
v2 go get -u github.com/taylorchu/generic/cmd/gorewrite
v1 (deprecated) go get -u github.com/taylorchu/generic/cmd/generic
This is an experiment to enable generics with code generation in the most elegant way.
Example
You can create a package like this. Note that Type
and TypeQueue
are type placeholders.
package queue
type Type string
// TypeQueue represents a queue of Type types.
type TypeQueue struct {
items []Type
}
// New makes a new empty Type queue.
func New() *TypeQueue {
return &TypeQueue{items: make([]Type, 0)}
}
// Enq adds an item to the queue.
func (q *TypeQueue) Enq(obj Type) *TypeQueue {
q.items = append(q.items, obj)
return q
}
// Deq removes and returns the next item in the queue.
func (q *TypeQueue) Deq() Type {
obj := q.items[0]
q.items = q.items[1:]
return obj
}
// Len gets the current number of Type items in the queue.
func (q *TypeQueue) Len() int {
return len(q.items)
}
Add rewrite config in GoRewrite.yaml
, and run gorewrite
:
spec:
- name: result
import: github.com/YourName/queue
typeMap:
Type:
expr: int64
TypeQueue:
expr: FIFO
The output is saved to $PWD/result/
.
package result
type FIFO struct {
items []int64
}
func New() *FIFO {
return &FIFO{items: make([]int64, 0)}
}
func (q *FIFO) Enq(obj int64) *FIFO {
q.items = append(q.items, obj)
return q
}
func (q *FIFO) Deq() int64 {
obj := q.items[0]
q.items = q.items[1:]
return obj
}
func (q *FIFO) Len() int {
return len(q.items)
}
GoRewrite.yaml
reference
The yaml config contains multiple rewrite specs.
spec[*].name
(string): unique identifier the spec. It is a path to the output, and used as package name if the spec is not local.spec[*].local
(bool): true if the spec is local. If the spec is local, the output will be saved in$PWD
instead of a new package relative to$PWD
. All the top level identifiers and the filename will also be prefixed withspec[*].name
to avoid conflicts.spec[*].typeMap
(map): type mappings used to replace placeholders. The key is type placeholder. The valueexpr
can be any go expression. Ifexpr
references any other packages, all those packages need to be listed inimport
.
spec:
- name: result
local: true
import: github.com/YourName/queue
typeMap:
Type:
expr: test.Box
import:
- github/YourName/test
FAQ
What are the existing approaches to generics in go?
This post summarizes the current state in go to "simulate" generics.
Why are type-checking and ast-based replacement important?
Type-checking and ast-based replacement ensure that the tool doesn't generate invalid code even you or the tool make mistakes, and rewrites identifiers in cases that it shouldn't.
Why is type placeholder designed this way?
type TypeXXX int32
- It provides a namespace for replaceable types.
- Knowing that this type might be replaced, package creator can still write go-testable code with a concrete type.
- It can express meaning. For example,
TypeQueue
shows that it is a queue.
Why does this tool rewrite at package-level instead of file-level?
- This tool tries NOT to apply any restriction for package creator except that any TypeXXX might be rewritten. Package creator has full flexibility to write normal go code.
- It is common to distribute go code at package-level.
Why does it remove all the comments?
Comments in go ast are free-floating, so they are hard to work with. Hopefully it is fixed in the near future.
How do I make sure the rewritten package is not import-able?
The spec name should start with internal/
. For example, internal/queue
.
When the go command sees an import of a package with internal in its path, it verifies that the package doing the import is within the tree rooted at the parent of the internal directory. For example, a package .../a/b/c/internal/d/e/f can be imported only by code in the directory tree rooted at .../a/b/c. It cannot be imported by code in .../a/b/g or in any other repository.
See Internal packages.
How do I determine spec name?
Spec name is essentially go package path, and the base name of this package path is a package name.
Good package names are short and clear. They are lower case, with no under_scores or mixedCaps.
See Package names.