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 main
import "fmt"
//go:generate ./command.sh
func 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/bash
echo "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 types
import (
"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 []*Person
func (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 main
import (
"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 <<EOL
package types
type ${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 :)

Like what you read? Give Edouard Tavinor a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.