Hygiene in templates

Per default templates are hygienic: Local identifiers declared in a template cannot be accessed in the instantiation context:

  1. template newException*(exceptn: typedesc, message: string): untyped =
  2. var
  3. e: ref exceptn # e is implicitly gensym'ed here
  4. new(e)
  5. e.msg = message
  6. e
  7. # so this works:
  8. let e = "message"
  9. raise newException(IoError, e)

Whether a symbol that is declared in a template is exposed to the instantiation scope is controlled by the inject and gensym pragmas: gensym’ed symbols are not exposed but inject’ed are.

The default for symbols of entity type, var, let and const is gensym and for proc, iterator, converter, template, macro is inject. However, if the name of the entity is passed as a template parameter, it is an inject’ed symbol:

  1. template withFile(f, fn, mode: untyped, actions: untyped): untyped =
  2. block:
  3. var f: File # since 'f' is a template param, it's injected implicitly
  4. ...
  5. withFile(txt, "ttempl3.txt", fmWrite):
  6. txt.writeLine("line 1")
  7. txt.writeLine("line 2")

The inject and gensym pragmas are second class annotations; they have no semantics outside of a template definition and cannot be abstracted over:

  1. {.pragma myInject: inject.}
  2. template t() =
  3. var x {.myInject.}: int # does NOT work

To get rid of hygiene in templates, one can use the dirty pragma for a template. inject and gensym have no effect in dirty templates.

gensym’ed symbols cannot be used as field in the x.field syntax. Nor can they be used in the ObjectConstruction(field: value) and namedParameterCall(field = value) syntactic constructs.

The reason for this is that code like

  1. type
  2. T = object
  3. f: int
  4. template tmp(x: T) =
  5. let f = 34
  6. echo x.f, T(f: 4)

should work as expected.

However, this means that the method call syntax is not available for gensym’ed symbols:

  1. template tmp(x) =
  2. type
  3. T {.gensym.} = int
  4. echo x.T # invalid: instead use: 'echo T(x)'.
  5. tmp(12)

Note: The Nim compiler prior to version 1 was more lenient about this requirement. Use the --useVersion:0.19 switch for a transition period.