skip to content

In Go's html/template, what do the {{define}} and {{template}} actions do, and when do you need ExecuteTemplate rather than Execute?

level: juniorimportance: must knowfreq 46%

answer

  1. one value, many named templates
  2. a set with a shared namespace
  3. the receiver has a name too
  4. select by name, or run the receiver

basics

~20 s

{{define "name"}}...{{end}} names a template inside the parsed text; {{template "name" .}} renders it in place with the data you give it. Execute runs the receiver template; ExecuteTemplate picks one associated template from the set by name.

solid answer

~50 s

A parsed `*template.Template` is not one template but a *set*: every `{{define "name"}}...{{end}}` adds another named template to a shared namespace, and `{{template "name" .}}` executes one of them inline, passing whatever pipeline follows the name as the new dot inside it. `Execute(w, data)` runs the template the receiver itself is named for, which is often not what you want: a file made of nothing but `{{define}}` blocks has an empty body of its own, and a template value created with `template.New("root")` has no body at all once files supply the names. `ExecuteTemplate(w, "name", data)` selects from the set by name, so it is the call you make when a layout and its pages were parsed together. `Lookup("name")` returns nil for a name that is not defined, which is the cheap way to check before executing one.

code

go · 6 lines
go
t := template.Must(template.New("root").Parse(
	`{{define "greeting"}}Hello, {{.Name}}!{{end}}`))

// t.Execute would run "root", whose own body is empty.
err := t.ExecuteTemplate(os.Stdout, "greeting", struct{ Name string }{"Ada"})
// prints: Hello, Ada!

go deeper

for a junior

Be ready to write the two-file shape by hand: one file of {{define}} blocks, a layout that calls {{template}}, one ExecuteTemplate naming what you want, and a check on the returned error.

for a middle

Explain that a parsed template value is a namespace of associated templates, that the receiver carries its own name, and what dot is inside an invoked template when no pipeline is supplied.

for a senior

Show how the set stays predictable in a service: parse once at startup under template.Must, render into a buffer so a mid-render error cannot emit half a page, and validate any externally supplied name with Lookup.

for a principal

Own the convention. Whether pages are addressed by file base name or by an explicit define name is a codebase-wide call that decides how badly a directory reshuffle can break rendering.

## One value, many templates The thing `template.New`, `Parse`, `ParseFiles` and `ParseFS` hand back is a `*template.Template`, and the mental model most people start with — "this variable is my template" — is wrong in a way that produces a very specific class of bug. The value is a *handle into a namespace*. It has its own name (`t.Name()`), and it is associated with every other template that was parsed alongside it. All of them share one map from name to body. `{{define "name"}} ... {{end}}` is what puts an entry in that map. It may only appear at the top level of the template text — you cannot nest a define inside an `{{if}}`, a `{{range}}`, or another define's body. Everything *outside* any define becomes the body of the template the text was parsed into, which is the receiver's own name. ## Calling one from another `{{template "name" pipeline}}` executes the associated template called `name` at that point and writes its output inline. The value of the pipeline after the name becomes dot inside the invoked template. Nothing is inherited: write `{{template "footer"}}` with no pipeline and dot is nil inside `footer`, so any `{{.Year}}` there fails at execution time. Forward the caller's data explicitly with `{{template "footer" .}}`, or narrow it with `{{template "footer" .Site}}`. The name in a `{{template}}` action is a string constant resolved at execution against the set. That is why a template can call one that is defined later, or in a different file, as long as both ended up in the same set. ## Execute versus ExecuteTemplate `Execute(w, data)` is shorthand for "run me" — the template the receiver is named for. `ExecuteTemplate(w, name, data)` runs the associated template of that name. Both return an error, and both may have already written bytes to the writer before failing, so a handler that writes straight to the response can emit a half-rendered page; rendering into a buffer first and copying it out on success is the usual defence. The standard library's own documentation warns about the mismatch: templates created by `ParseFiles` are named by the base names of the files, so if you started from `template.New("root")` and no file is called `root`, the receiver has no body and `Execute` fails. The fix it recommends is exactly `ExecuteTemplate` with a name you know is in the set. Equally, if a page file contains only a define, executing *it* by its file name writes nothing, because its own body is empty. ## template.Must and where parsing belongs `template.Must(t, err)` takes the two results of a parse, panics if the error is non-nil, and returns the template otherwise. Its whole purpose is to let parsing live in a package-level variable or an init step, so a malformed template stops the process at startup instead of surfacing as an error from a request handler that nobody reads. In a long-running service you parse once, at startup, and treat the resulting set as read-only while requests are served — execution is safe from many goroutines, mutation is not. ## Lookup and the defined set `t.Lookup(name)` returns the associated template with that name, or nil. `t.Templates()` returns them all, and `t.DefinedTemplates()` returns a printable string listing the names, which is what the package itself puts into error messages. If a name ever comes from outside the program — a page identifier in a URL, say — you check it against the set with `Lookup` rather than passing it straight to `ExecuteTemplate`. ## The shape to remember A layout file holds the surrounding document and calls `{{template "content" .}}`. Each page file holds `{{define "content"}} ... {{end}}`. You parse the layout together with one page, and you call `ExecuteTemplate(w, "layout.html", data)` — the layout, not the page. Get those two facts right and most template composition problems never appear.

  • What is dot inside a template invoked as {{template "footer"}} with nothing after the name?
    Nil. The pipeline written after the name becomes the new dot for the invoked template, and omitting it does not forward the caller's dot. Any `{{.Field}}` inside then fails at execution. Write `{{template "footer" .}}` to pass the current data through, or `{{template "footer" .Site}}` to hand it a narrower value.
  • What does template.Must add, and why is it used at package level?
    `template.Must(t, err)` panics when err is non-nil and returns t otherwise. It exists so parsing can happen in a package-level variable or an init step: a malformed template then kills the process at startup, where a deploy will notice, instead of returning an error from a handler at 3am. It is only appropriate where a failure genuinely should be fatal.
  • Where in the template text may a {{define}} action appear?
    Only at the top level. You cannot put a define inside an `{{if}}`, a `{{range}}`, or another define's body. Text outside every define becomes the body of the template the text was parsed into; if a file contains nothing but defines and whitespace, that body is empty and executing it by name renders nothing.

A parsed template value is a card catalogue rather than a single book. Execute reads the one book the catalogue itself is filed under; ExecuteTemplate asks for any title on the shelf.

saying these in an interview costs you the question

  • Thinks a parsed template value holds exactly one template
  • Calls Execute and expects a named block to render
  • Believes {{template}} inherits the caller's dot automatically
  • Puts a {{define}} inside a range or if body
  • Ignores the error returned by Execute or ExecuteTemplate