Special Types ============= static[T] --------- **Note**: static[T] is still in development. As their name suggests, static parameters must be known at compile-time: .. code-block:: nim proc precompiledRegex(pattern: static[string]): RegEx = var res {.global.} = re(pattern) return res precompiledRegex("/d+") # Replaces the call with a precompiled # regex, stored in a global variable precompiledRegex(paramStr(1)) # Error, command-line options # are not known at compile-time For the purposes of code generation, all static params are treated as generic params - the proc will be compiled separately for each unique supplied value (or combination of values). Static params can also appear in the signatures of generic types: .. code-block:: nim type Matrix[M,N: static[int]; T: Number] = array[0..(M*N - 1), T] # Note how `Number` is just a type constraint here, while # `static[int]` requires us to supply a compile-time int value AffineTransform2D[T] = Matrix[3, 3, T] AffineTransform3D[T] = Matrix[4, 4, T] var m1: AffineTransform3D[float] # OK var m2: AffineTransform2D[string] # Error, `string` is not a `Number` typedesc -------- `typedesc` is a special type allowing one to treat types as compile-time values (i.e. if types are compile-time values and all values have a type, then typedesc must be their type). When used as a regular proc param, typedesc acts as a type class. The proc will be instantiated for each unique type parameter and one can refer to the instantiation type using the param name: .. code-block:: nim proc new(T: typedesc): ref T = echo "allocating ", T.name new(result) var n = Node.new var tree = new(BinaryTree[int]) When multiple typedesc params are present, they act like a distinct type class (i.e. they will bind freely to different types). To force a bind-once behavior one can use a named alias or an explicit `typedesc` generic param: .. code-block:: nim proc acceptOnlyTypePairs[T: typedesc, U: typedesc](A, B: T; C, D: U) Once bound, typedesc params can appear in the rest of the proc signature: .. code-block:: nim template declareVariableWithType(T: typedesc, value: T) = var x: T = value declareVariableWithType int, 42 When used with macros and .compileTime. procs on the other hand, the compiler does not need to instantiate the code multiple times, because types then can be manipulated using the unified internal symbol representation. In such context typedesc acts as any other type. One can create variables, store typedesc values inside containers and so on. For example, here is how one can create a type-safe wrapper for the unsafe `printf` function from C: .. code-block:: nim macro safePrintF(formatString: string{lit}, args: varargs[expr]): expr = var i = 0 for c in formatChars(formatString): var expectedType = case c of 'c': char of 'd', 'i', 'x', 'X': int of 'f', 'e', 'E', 'g', 'G': float of 's': string of 'p': pointer else: EOutOfRange var actualType = args[i].getType inc i if expectedType == EOutOfRange: error c & " is not a valid format character" elif expectedType != actualType: error "type mismatch for argument ", i, ". expected type: ", expectedType.name, ", actual type: ", actualType.name # keep the original callsite, but use cprintf instead result = callsite() result[0] = newIdentNode(!"cprintf") Overload resolution can be further influenced by constraining the set of types that will match the typedesc param: .. code-block:: nim template maxval(T: typedesc[int]): int = high(int) template maxval(T: typedesc[float]): float = Inf var i = int.maxval var f = float.maxval var s = string.maxval # error, maxval is not implemented for string The constraint can be a concrete type or a type class.