go generate
Hello! this is a short post about using go generate to generate code.
go generate is a tool that comes with the go command. You write go generate directives in your source code and then run the directives by calling go generate on the command line (or using your IDE). Here’s an example main.go:
package mainimport "fmt"//go:generate ./command.shfunc main() {
fmt.Println("if you type 'go generate' in this directory command.sh will be run")
}
And here’s a minimal command.sh in the same directory as main.go:
#!/bin/bashecho "hello world!"
Now I can run the code as follows:
$ ls
main.go command.sh
$ chmod +x command.sh
$ go generate
hello world!
go generate also accepts a couple of flags. The only one I use regularly is -x, which prints a list of the commands as they are executed to STDOUT.
$ go generate -x
./command.sh
hello world!
And that’s the basic usage of go generate. Now to make it do something useful. Let’s say we want to work on slices of struct pointers. We have the following code in the file ./types/person.go:
package typesimport (
"fmt"
)type Person struct {
Name string
Age int
}func (p *Person)String() string {
return fmt.Sprintf("%v (%v)", p.Name, p.Age)
}type PersonToBool func(*Person) bool
type PersonList []*Personfunc (pl PersonList)Filter(f PersonToBool) PersonList {
var ret []*Person
for _, p := range pl {
if f(p) {
ret = append(ret, p)
}
}
return ret
}
And this is our new main.go:
package mainimport (
"fmt"
"./types"
)func main() {
var pl types.PersonList
pl = append(pl, &types.Person{Name:"Jane", Age:32})
pl = append(pl, &types.Person{Name:"Ed", Age:27}) pl2 := pl.Filter( func(p *types.Person) bool {
return p.Age>30
}) for _, p := range pl2 {
fmt.Println(p)
}
}
We run the code:
$ ls
main.go types
$ go run
Jane (32)
Let’s say we also have an Address struct. This is the code for ./types/address.go:
package types
import "fmt"
type Address struct {
Street string
Town string
}
func (a *Address)String() string {
return fmt.Sprintf("%v\n%v", a.Street, a.Town)
}
type AddressList []*Address
type AddressToBool func(*Address) bool
func (al AddressList)Filter(f AddressToBool) AddressList {
var ret AddressList
for _, a := range al {
if f(a) {
ret = append(ret, a)
}
}
return ret
}
As you can see, it looks almost exactly like ./types/person.go. Is there a way we can make a generic Filter method?
We can generate the code by calling another program. Here’s the code for ./types/newList.sh:
#!/bin/sh
TYPE=$1
cat > ${TYPE}List.go <<EOLpackage typestype ${TYPE}List []*${TYPE}
type ${TYPE}ToBool func(*${TYPE}) bool
func (al ${TYPE}List)Filter(f ${TYPE}ToBool) ${TYPE}List {
var ret ${TYPE}List
for _, a := range al {
if f(a) {
ret = append(ret, a)
}
}
return ret
}
EOL
And our ./types/person.go becomes:
package types
import "fmt"
//go:generate ./newList.sh Person
type Person struct {
Name string
Age int
}
func (p *Person)String() string {
return fmt.Sprintf("%v (%v)", p.Name, p.Age)
}
Now we can run
$ cd types
$ chmod +x newList.sh
$ go generate
$ cd ..
$ go run
jane (32)
So it all works! But there’s a problem. With a very simple shell script, debugging the code to generate the PersonList struct and the Filter method will be easy enough, but it will get difficult the longer the code is. It would be better to create a dummy struct and use a standard go file. Let’s create a file ./types/list.tmpl.go:
package types
type DUMMYTYPEList []*DUMMYTYPE
type DUMMYTYPEToBool func(*DUMMYTYPE) bool
func (al DUMMYTYPEList)Filter(f DUMMYTYPEToBool) DUMMYTYPEList {
var ret DUMMYTYPEList
for _, a := range al {
if f(a) {
ret = append(ret, a)
}
}
return ret
}
And a small file ./types/dummyType.go:
package types
type DUMMYTYPE interface{}
Now we get syntax highlighting in our IDE! We just have to write a short shell command to exchange DUMMYTYPE with the name of another type. This is the new ./types/newList.sh:
#!/bin/sh
TYPE=$1
cat list.tmpl.go | sed -e 's/DUMMYTYPE/'${TYPE}'/g' > ${TYPE}List.go
We can run the code as above:
$ cd types
$ go generate
$ cd ..
$ go run
Jane (32)
Of course, you could extend the shell script to allow for multiple class arguments for methods like MapToSomeOtherType, or you could use a totally different language to write the commands called by go generate. Unfortunately I can’t see a way to use go templates to do this, because go templates are not valid go source code files.
I hope this helps somebody :)