typedesc[T]

In many contexts, Nim allows to treat the names of types as regular values. These values exists only during the compilation phase, but since all values must have a type, typedesc is considered their special type.

typedesc acts like a generic type. For instance, the type of the symbol int is typedesc[int]. Just like with regular generic types, when the generic param is omitted, typedesc denotes the type class of all types. As a syntactic convenience, one can also use typedesc as a modifier.

Procs featuring typedesc params are considered implicitly generic. They will be instantiated for each unique combination of supplied types and within the body of the proc, the name of each param will refer to the bound concrete type:

  1. proc new(T: typedesc): ref T =
  2. echo "allocating ", T.name
  3. new(result)
  4. var n = Node.new
  5. var tree = new(BinaryTree[int])

When multiple type params are present, they will bind freely to different types. To force a bind-once behavior one can use an explicit generic param:

  1. proc acceptOnlyTypePairs[T, U](A, B: typedesc[T]; C, D: typedesc[U])

Once bound, type params can appear in the rest of the proc signature:

  1. template declareVariableWithType(T: typedesc, value: T) =
  2. var x: T = value
  3. declareVariableWithType int, 42

Overload resolution can be further influenced by constraining the set of types that will match the type param. This works in practice to attaching attributes to types via templates. The constraint can be a concrete type or a type class.

  1. template maxval(T: typedesc[int]): int = high(int)
  2. template maxval(T: typedesc[float]): float = Inf
  3. var i = int.maxval
  4. var f = float.maxval
  5. when false:
  6. var s = string.maxval # error, maxval is not implemented for string
  7. template isNumber(t: typedesc[object]): string = "Don't think so."
  8. template isNumber(t: typedesc[SomeInteger]): string = "Yes!"
  9. template isNumber(t: typedesc[SomeFloat]): string = "Maybe, could be NaN."
  10. echo "is int a number? ", isNumber(int)
  11. echo "is float a number? ", isNumber(float)
  12. echo "is RootObj a number? ", isNumber(RootObj)

Passing typedesc almost identical, just with the differences that the macro is not instantiated generically. The type expression is simply passed as a NimNode to the macro, like everything else.

  1. import macros
  2. macro forwardType(arg: typedesc): typedesc =
  3. # ``arg`` is of type ``NimNode``
  4. let tmp: NimNode = arg
  5. result = tmp
  6. var tmp: forwardType(int)