Go Code Generation Cheatsheet

July 2019 ยท 2 minute read

Golang is blamed for lacking of generics, and the solution for that is code generation. Surely reflection could also do the job, but code generation has 3 advantages:

  1. Generated code could be made type-safe, you don’t want to see interface{} everywhere
  2. Generated code runs faster, no runtime overhead
  3. Generated code is easier to understand and less cryptic

Basically the only library needed is text/template, and skeleton code looks like(error handling omitted):

t := template.New("exampleTemplate")
t, _ = t.Parse(fileHeader)
t, _ = t.Parse(file)
t, _ = t.Parse(someOtherDefinition)
_ = t.ExecuteTemplate(file, "TemplateEntryName", toRender)

First create a new template with name exampleTemplate, then add template definitions to it. A template definition works like a function, you could call it in another template. For example:

const fileHeader = `
{{define "FileHeader"}}
// This file is generated. DO NOT EDIT
{{end}}
`
const someOtherDefinition = `
{{define "SomeOtherDefinition"}}
{{ .Name }} := {{ .Value }}
{{end}}
`
const file = `
{{define "TemplateEntryName"}}

{{template "FileHeader"}}

{{range .}}
	{{template "SomeOtherDefinition" .}}
{{end}}

{{end}}
`

When t.ExecuteTemplate is called, the template engine would first evaluate TemplateEntryName defined in file variable. TemplateEntryName calls the other two templates, FileHeader and SomeOtherDefinition. FileHeader prints a header declaring the file is generated, and SomeOtherDefinition is more interesting because it accepts a variable. According to the example above, type of toRender could be:

type structToRender struct {
    Name  string
    Value string
}
var toRender []structToRender

In TemplateEntryName, {{range .}} would get a variable typed []structToRender as ., and {{template "SomeOtherDefinition" .}} would get a variable typed structToRender. Then inside SomeOtherDefinition, .Name and .Value both have type string.

And a few more tips: