skip to content

Which Go template builtins look up a dynamic map key, format a value, and invoke a function value?

level: middleimportance: nice to knowfreq 32%

answer

  1. three builtins, three different jobs
  2. field syntax cannot spell a hyphen
  3. one of them is fmt.Sprintf renamed
  4. a func in a struct field is not a name
  5. index x 1 2 means x[1][2]

basics

~20 s

index looks up a map, slice or array by a key or position computed at render time; printf is the template name for fmt.Sprintf; call invokes a function value held in the data with the remaining arguments. All three are always available, with no registration.

solid answer

~50 s

Templates ship a small set of builtins, and three of them cover most of what people reach for a custom helper to do. `{{index .Fields "content-type"}}` indexes a map, slice or array by an argument, which is the only way to reach a key that is not a valid identifier or that is computed at render time — `{{.Fields.content-type}}` is not even parseable. `{{printf "%6.2f" .AmountDue}}` is `fmt.Sprintf` under a template name, and it is what pipelines end in when you need formatting rather than a new helper. `{{call .Format .AmountDue}}` calls a function *value* carried in the data — `index` and `printf` are called by name, but a `func` sitting in a struct field needs `call` to be invoked, and it obeys the same one-or-two-results rule as a func-map entry. Reaching for these before registering a helper keeps the func map small.

code

text · 3 lines
text
{{index .Headers "content-type"}}
{{printf "%6.2f" .AmountDue}}
{{call .Format .AmountDue}}

go deeper

for a junior

Know that templates come with builtins you never register, and that index is how you read a map entry whose key is a string argument rather than a field name.

for a middle

Be able to say what each of the three does in Go terms: index is subscripting, printf is fmt.Sprintf, call invokes a function value held in the data. Explain why a func in a struct field cannot be called by name.

for a senior

Show that you prefer builtins over new registrations, and know the failure modes: an out-of-range index kills the render while a mismatched printf verb silently writes noise into the document.

for a principal

Argue the API angle: every helper you add is a name template authors will depend on forever, so leaning on documented builtins keeps the surface you must support small.

## Builtins are the functions you never register Every template can call a fixed set of functions without a `template.FuncMap`: `and`, `or`, `not`, `len`, `index`, `slice`, `printf`, `print`, `println`, `call`, and the escapers `html`, `js`, `urlquery`. Knowing them well is what keeps a func map from filling up with things the language already gives you. Three are worth real fluency. ## `index` — subscripting from template source `index x a b c` is `x[a][b][c]` in Go syntax; each indexed value must be a map, slice, array or pointer to one. Two situations need it: - **A key that is not an identifier.** Field syntax cannot express it: `{{.Headers.content-type}}` does not parse, because `-` is not part of an identifier. `{{index .Headers "content-type"}}` does. - **A key computed at render time.** Inside a `range` you may hold the key in a variable: `{{index $totals $code}}`. On a slice, `index` takes an integer position and panics out of the render if it is out of range, so guard with `len` when the index is not known good. A missing map key yields the map's zero value rather than an error. `slice` is its neighbour: `slice x 1 3` is `x[1:3]`. ## `printf` — `fmt.Sprintf` by another name `printf` takes a format string and arguments and returns the formatted string, exactly as `fmt.Sprintf` does — same verbs, same behaviour when the verbs do not match the arguments (you get `%!d(string=...)` in the output rather than an error). It is the reason most "format this number" helpers never need to be written: ``` Amount due: {{printf "%6.2f" .AmountDue}} ``` It also composes in a pipeline as the final stage, since the value flowing down the pipe becomes the last argument: `{{.AmountDue | printf "%6.2f"}}`. `print` and `println` are `fmt.Sprint` and `fmt.Sprintln`. ## `call` — invoking a function carried in the data A name in an action is looked up in the func map and the builtins. A function *value* stored in the data is not a name, so it cannot be called that way. `call` bridges the gap: `call .Format .AmountDue` is `dot.Format(dot.AmountDue)` in Go syntax, where `.Format` holds something like a `func(int64) string`. The called function follows the familiar rule — one result, or two of which the second is `error`, and a non-nil error stops the render and comes back from `Execute`. This matters when the behaviour is per-render rather than per-template: a formatter chosen by the caller's locale, or a lookup closed over data you did not want to pass separately, can travel in the data value instead of forcing you to rebuild the template with a different func map. ## When to reach past the builtins Register a helper when the operation is domain-shaped — money in your house format, a business-rule pluralisation, a lookup against a table only the server has. Use the builtins when the operation is generic. The practical benefit is not tidiness: every name in your func map is a name that template authors will use, will expect to keep working, and will file bugs about. `printf` and `index` are already documented and already stable. ## Small traps - `index` with too few arguments, or on a value that is not indexable, fails the render rather than printing nothing. - `printf` with mismatched verbs quietly writes `%!s(int=3)`-style noise into the document; it is not an error, so it survives to production unless someone reads the output. - `call` on a nil function value fails the render. - `len` is the builtin for length; there is no `cap`.

  • Why can't you write {{.Headers.content-type}} for a map key with a hyphen?
    Field syntax after a dot must be a Go identifier, and `-` cannot appear in one, so the action does not even parse. `{{index .Headers "content-type"}}` passes the key as a string argument instead. The same applies to any key computed at render time — a key held in a range variable can only be reached through `index`.
  • What does a two-result function invoked through call do when its error is non-nil?
    Exactly what a func-map helper does: execution stops at that action and `Execute` returns the error, wrapped with the template name and the position of the action. A function value reached through `call` is bound by the same one-or-two-results rule, so it is a legitimate way to let the caller supply failure-capable behaviour per render.
  • What happens if printf's verbs don't match its arguments?
    You get the same thing `fmt.Sprintf` produces — a marker like `%!d(string=abc)` written into the document. It is not an error and it does not stop the render, so a mismatched verb reaches production unless a test compares rendered output. That is a good reason to snapshot-test the rendered result of templates that do their own formatting.

saying these in an interview costs you the question

  • Thinks index only works on slices, not maps
  • Believes a hyphenated map key is reachable with dot syntax
  • Registers a helper that just wraps fmt.Sprintf
  • Expects a func value in the data to be callable by name
  • Assumes a mismatched printf verb returns an error