typedesc[T]

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

typedesc acts as a generic type. For instance, the type of the symbol int is typedesc[int]. Just like with regular generic types, when the generic parameter 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 parameters 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 parameter 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 parameters are present, they will bind freely to different types. To force a bind-once behavior, one can use an explicit generic parameter:

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

Once bound, type parameters 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 parameter. This works in practice by 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 is almost identical, just with the difference that the macro is not instantiated generically. The type expression is simply passed as a NimNode to the macro, like everything else.

  1. import std/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)