Thoughts on Whatnot

A blog about .

Redefining Go Templates

In Go 1.6, a new template action was introduced in the text/template package that allows for both defining and executing a template at the same time. This action, block, seems at first to be somewhat pointless. Even the docs describe it as simply being shorthand for defining and then immediately executing a template, and what’s the point of that? Why would you want to execute an inline template immediately after defining it? Sure, maybe you’d want to reuse part of a page somewhere else, but is an extra action really necessary for that?

The answer comes in the form of redefining templates. Inline templates can be defined either through the block action, as mentioned previously, or through the define directive. In either case, however, defining the template twice inside the same template causes an error. For example:

{{define "example"}}
	First definition.
{{end}}
{{define "example"}}
	Second definition.
{{end}}

When the template engine reaches the second definition of example, it will fail, claiming, rightly, multiple definitions of a template. However, there is a workaround: If the inline template is redefined inside a different template, the engine won’t complain. For example:

const (
	tmplExample1 = `{{define "example"}}First definition.{{end}}`
	tmplExample2 = `{{define "example"}}Second definition.{{end}}`
)

func main() {
	tmpl := template.Must(template.New("example1").Parse(tmplExample1))
	tmpl = template.Must(tmpl.New("example2").Parse(tmplExample2))
	tmpl.ExecuteTemplate(os.Stdout, "example1", nil)
}

The engine now happily parses both templates, and promptly outputs nothing when executed, as neither of them are actually used. This is where the block action comes in handy. Consider the following modification to the above code:

const (
	tmplExample1 = `{{define "example"}}First definition.{{end}}{{template "example" .}}`
	tmplExample2 = `{{define "example"}}Second definition.{{end}}`
)

func main() {
	tmpl := template.Must(template.New("example1").Parse(tmplExample1))
	tmpl = template.Must(tmpl.New("example2").Parse(tmplExample2))
	tmpl.ExecuteTemplate(os.Stdout, "example1", nil)
}

This code now has some actual output. But, interestingly, it outputs ‘Second definition.’ This roundabout method of redefining has some very useful uses. Which brings us back to that redundant block action. Its sole purpose is making this pattern cleaner to use. In the above example, it’s quite possible to simply replace the define and template actions with a single block, like so:

tmplExample1 = `{{block "example" .}}First definition.{{end}}`

This is functionally identical, but is much cleaner for two simple reasons:

  • It helps eliminate potential whitespace issues. In the previous example, any whitespace in between the {{end}} of the define and the template action would be inserted in the output, potentially causing issues. Go 1.7 introduced a means of trimming whitespace around template directives, but before that, block was the only solution besides writing potentially very ugly templates.
  • It removes the need for a double usage of the same template, thus reducing potential issues caused later by refactoring.

For an example of this in use in the wild, see the bootstrap4-blog Hugo theme. I was perusing its code when I stumbled across a usage of this somewhat poorly documented Go template feature and was confused. ‘What’, I wondered, ‘was the point of that?’ They appeared to be simply defining an empty template! I became more confused as I found that another template seemed to just be defining a template and not using it anywhere. Worse, it was defining it under the same name that the other template did, which doesn’t work, right? Well, it turns out that that particular usage is pulled nearly verbatim from the Hugo docs. Hugo is designed to specifically allow this usage, a design decision which boosts Hugo’s flexibility by enourmous amounts.

The block action appears to be pointless, but it’s still a very welcome entry in the Go template toolset. Its pointlessness feature-wise is contrasted by the way it encourages the use of template redefinition. The power provided by template redefinitions is, unfortunately, not documented clearly in the official documentation, but it is one which should not be overlooked.


Share

comments powered by Disqus