|
|
@@ -0,0 +1,1088 @@
|
|
|
+# Chapter 16. Type system
|
|
|
+
|
|
|
+All expressions have a type that is known during semantic analysis. Nim is
|
|
|
+statically typed. One can declare new types, which is, in essence, defining an
|
|
|
+identifier that can be used to denote this custom type.
|
|
|
+These are the major type classes:
|
|
|
+ • ordinal types: consist of integer, bool, character, enumeration (and
|
|
|
+ subranges thereof) types
|
|
|
+ • floating-point types
|
|
|
+ • string type
|
|
|
+ • structured types
|
|
|
+ • reference (pointer) type
|
|
|
+ • procedural type
|
|
|
+ • generic type
|
|
|
+16.1. Ordinal types
|
|
|
+Ordinal types have the following characteristics:
|
|
|
+ • Ordinal types are countable and ordered. This property allows the
|
|
|
+ operation of functions such as inc, ord, and dec on ordinal types to be
|
|
|
+ defined.
|
|
|
+ • Ordinal types have a smallest possible value, accessible with low(type).
|
|
|
+ Trying to count further down than the smallest value produces a panic
|
|
|
+ or a static error.
|
|
|
+ • Ordinal types have a largest possible value, accessible with high(type).
|
|
|
+ Trying to count further up than the largest value produces a panic or a
|
|
|
+ 77
|
|
|
+ static error.
|
|
|
+Integers, bool, characters, and enumeration types (and subranges of these
|
|
|
+types) belong to ordinal types.
|
|
|
+A distinct type is an ordinal type if its base type is an ordinal type.
|
|
|
+16.2. Pre-defined integer types
|
|
|
+These integer types are pre-defined:
|
|
|
+int
|
|
|
+ The generic signed integer type; its size is platform-dependent and has
|
|
|
+ the same size as a pointer. This type should be used in general. An integer
|
|
|
+ literal that has no type suffix is of this type if it is in the range
|
|
|
+ low(int32)..high(int32) otherwise the literal’s type is int64.
|
|
|
+intN
|
|
|
+ Additional signed integer types of N bits use this naming scheme
|
|
|
+ (example: int16 is a 16-bit wide integer). The current implementation
|
|
|
+ supports int8, int16, int32, int64. Literals of these types have the suffix
|
|
|
+ 'iN.
|
|
|
+uint
|
|
|
+ The generic unsigned integer type; its size is platform-dependent and has
|
|
|
+ the same size as a pointer. An integer literal with the type suffix 'u is of
|
|
|
+ this type.
|
|
|
+uintN
|
|
|
+ Additional unsigned integer types of N bits use this naming scheme
|
|
|
+ (example: uint16` is a 16-bit wide unsigned integer). The current
|
|
|
+ implementation supports uint8, uint16, uint32, uint64. Literals of these
|
|
|
+ types have the suffix 'uN. Unsigned operations all wrap around; they
|
|
|
+ cannot lead to over- or underflow errors.
|
|
|
+Automatic type conversions are performed in expressions where different
|
|
|
+kinds of integer types are used: the smaller type is converted to the larger.
|
|
|
+A narrowing type conversion converts a larger to a smaller type (for example
|
|
|
+int32 → int16). A widening type conversion converts a smaller type to a larger
|
|
|
+type (for example int16 → int32). In Nim only widening type conversions are
|
|
|
+78
|
|
|
+implicit:
|
|
|
+ var myInt16 = 5'i16
|
|
|
+ var myInt: int
|
|
|
+ echo myInt16 + 34'i8 # of type `int16`
|
|
|
+ echo myInt16 + myInt # of type `int`
|
|
|
+ echo myInt16 + 2'i32 # of type `int32`
|
|
|
+For further details, see Section 17.3, “Convertible relation”.
|
|
|
+Table 5. Integer operations
|
|
|
+ Operation Meaning
|
|
|
+ + Integer addition
|
|
|
+ - Integer subtraction
|
|
|
+ * Integer multiplication
|
|
|
+ inc Increment an integer / an ordinal type
|
|
|
+ dec Decrement an integer / an ordinal type
|
|
|
+ div Integer division
|
|
|
+ mod Integer modulo (remainder)
|
|
|
+ shl Shift left
|
|
|
+ shr Shift right
|
|
|
+ ashr Arithmetic shift right
|
|
|
+ and Bitwise and
|
|
|
+ or Bitwise or
|
|
|
+ xor Bitwise xor
|
|
|
+ not Bitwise not (complement)
|
|
|
+16.3. Integer literals
|
|
|
+int literals are implicitly convertible to a smaller integer type if the literal’s
|
|
|
+value fits this smaller type and such a conversion is less expensive than other
|
|
|
+implicit conversions, so myInt16 + 34 produces an int16 result.
|
|
|
+ 79
|
|
|
+16.4. Subrange types
|
|
|
+A subrange type is a range of values from an ordinal or floating-point type
|
|
|
+(the base type). To define a subrange type, one must specify its limiting
|
|
|
+values — the lowest and highest value of the type. For example:
|
|
|
+ type
|
|
|
+ Subrange = range[0..5]
|
|
|
+ PositiveFloat = range[0.0..Inf]
|
|
|
+ Positive* = range[1..high(int)] # as defined in `system`
|
|
|
+Subrange is a subrange of an integer which can only hold the values 0 to 5.
|
|
|
+PositiveFloat defines a subrange of all positive floating-point values. NaN
|
|
|
+does not belong to any subrange of floating-point types. Assigning any other
|
|
|
+value to a variable of type Subrange is a panic (or a static error if it can be
|
|
|
+determined during semantic analysis). Assignments from the base type to
|
|
|
+one of its subrange types (and vice versa) are allowed.
|
|
|
+A subrange type has the same size as its base type (int in the Subrange
|
|
|
+example).
|
|
|
+16.5. Pre-defined floating-point types
|
|
|
+The following floating-point types are pre-defined:
|
|
|
+float
|
|
|
+ The generic floating-point type; its size used to be platform-dependent,
|
|
|
+ but now it is always mapped to float64. This type should be used in
|
|
|
+ general.
|
|
|
+floatN
|
|
|
+ Nim defines floating-point types of N bits using this naming scheme
|
|
|
+ (example: float64 is a 64-bit wide float). The current implementation
|
|
|
+ supports float32 and float64. Literals of these types have the suffix 'fN.
|
|
|
+Automatic type conversion in expressions with different kinds of floating-
|
|
|
+point types is performed: See Section 17.3, “Convertible relation” for further
|
|
|
+details. Arithmetic performed on floating-point types follows the IEEE
|
|
|
+standard. Integer types are not converted to floating-point types
|
|
|
+automatically and vice versa.
|
|
|
+80
|
|
|
+The IEEE standard defines five types of floating-point exceptions:
|
|
|
+ • Invalid: operations with mathematically invalid operands, for example:
|
|
|
+ 0.0/0.0, sqrt(-1.0), and log(-37.8).
|
|
|
+ • Division by zero: divisor is zero and dividend is a finite nonzero number,
|
|
|
+ for example 1.0/0.0.
|
|
|
+ • Overflow: operation produces a result that exceeds the range of the
|
|
|
+ exponent, for example MAXDOUBLE+0.0000000000001e308.
|
|
|
+ • Underflow: operation produces a result that is too small to be
|
|
|
+ represented as a normal number, for example: MINDOUBLE * MINDOUBLE.
|
|
|
+ • Inexact: operation produces a result that cannot be represented with
|
|
|
+ infinite precision, for example: 2.0 / 3.0, log(1.1) and 0.1 in input.
|
|
|
+The IEEE exceptions are either ignored during execution or mapped to the
|
|
|
+Nim exceptions: FloatInvalidOpDefect, FloatDivByZeroDefect,
|
|
|
+FloatOverflowDefect, FloatUnderflowDefect, and FloatInexactDefect. These
|
|
|
+exceptions inherit from the FloatingPointDefect base class.
|
|
|
+Table 6. Float operations
|
|
|
+ Operation Meaning
|
|
|
+ + Float addition
|
|
|
+ - Float subtraction
|
|
|
+ * Float multiplication
|
|
|
+ / Float division
|
|
|
+16.5.1. Nan and Inf checks
|
|
|
+The Nim compiler provides the pragmas nanChecks and infChecks to control
|
|
|
+whether the IEEE exceptions are ignored or trap a Nim exception:
|
|
|
+ {.nanChecks: on, infChecks: on.}
|
|
|
+ var a = 1.0
|
|
|
+ var b = 0.0
|
|
|
+ echo b / b # raises FloatInvalidOpDefect
|
|
|
+ echo a / b # raises FloatOverflowDefect
|
|
|
+In the current implementation FloatDivByZeroDefect and FloatInexactDefect
|
|
|
+ 81
|
|
|
+are never raised. FloatOverflowDefect is raised instead of
|
|
|
+FloatDivByZeroDefect. There is also a floatChecks pragma that is a short-cut
|
|
|
+for the combination of nanChecks and infChecks pragmas. floatChecks are
|
|
|
+turned off as default.
|
|
|
+The only operations that are affected by the floatChecks pragma are the +, -,
|
|
|
+*, / operators for floating-point types.
|
|
|
+The Nim compiler uses the maximum precision available to evaluate
|
|
|
+floating-point values during semantic analysis; this means expressions like
|
|
|
+0.09'f32 + 0.01'f32 == 0.09'f64 + 0.01'f64 that are evaluating during
|
|
|
+constant folding are true.
|
|
|
+16.6. Boolean type
|
|
|
+The boolean type is named bool in Nim and can be one of the two pre-defined
|
|
|
+values true and false. Conditions in while, if, elif, when statements need to
|
|
|
+be of type bool.
|
|
|
+This condition holds:
|
|
|
+ ord(false) == 0 and ord(true) == 1
|
|
|
+The operators not, and, or, xor, <, <=, >, >=, !=, == are defined for the
|
|
|
+bool type. The and and or operators perform short-cut evaluation. Example:
|
|
|
+ while p != nil and p.name != "xyz":
|
|
|
+ # p.name is not evaluated if p == nil
|
|
|
+ p = p.next
|
|
|
+The size of the bool type is one byte.
|
|
|
+82
|
|
|
+ It can be a good idea to use a custom enum type instead of bool
|
|
|
+ even if the enum has only two possible values. Compare:
|
|
|
+ proc deleteFile(f: string): bool
|
|
|
+
|
|
|
+ To:
|
|
|
+ type
|
|
|
+ Status = enum
|
|
|
+ Failure,
|
|
|
+ Success
|
|
|
+ proc deleteFile(f: string): Status
|
|
|
+16.7. Character type
|
|
|
+The character type is named char in Nim. Its size is one byte. Thus it cannot
|
|
|
+represent a UTF-8 character, but a part of it.
|
|
|
+The standard library offers a Rune type, that can represent any Unicode
|
|
|
+character, in its unicode module.
|
|
|
+16.8. Enumeration types
|
|
|
+Enumeration types define a new type whose values consist of the ones
|
|
|
+specified. The values are ordered. Example:
|
|
|
+ type
|
|
|
+ Direction = enum
|
|
|
+ north, east, south, west
|
|
|
+ assert ord(north) == 0
|
|
|
+ assert ord(east) == 1
|
|
|
+ assert ord(south) == 2
|
|
|
+ assert ord(west) == 3
|
|
|
+ # Also allowed:
|
|
|
+ assert ord(Direction.west) == 3
|
|
|
+The implied order is: north < east < south < west. The comparison operators
|
|
|
+can be used with enumeration types. Instead of north etc, the enum value can
|
|
|
+ 83
|
|
|
+also be qualified with the enum type that it resides in, Direction.north.
|
|
|
+For better interfacing to other programming languages, the fields of enum
|
|
|
+types can be assigned an explicit ordinal value. However, the ordinal values
|
|
|
+have to be in ascending order. A field whose ordinal value is not explicitly
|
|
|
+given is assigned the value of the previous field + 1.
|
|
|
+ Idiomatic Nim code makes heavy use of enum types. If you use
|
|
|
+ an enum in a case statement, the compiler enforces that every
|
|
|
+ possible enum value is handled explicitly (unless an else
|
|
|
+ section is present). The value of thinking about every possible
|
|
|
+ case can hardly be overstated, it makes for more robust
|
|
|
+ software that is easy to maintain: If a new state is added, all the
|
|
|
+ places in a codebase that need to be considered are listed by
|
|
|
+ the compiler’s error messages. This is far preferable to a more
|
|
|
+ object oriented approach where the dispatching is distributed
|
|
|
+ over multiple files and there is no enforcement if classes do
|
|
|
+ override a virtual method.
|
|
|
+An explicit ordered enum can have holes:
|
|
|
+ type
|
|
|
+ TokenType = enum
|
|
|
+ a = 2, b = 4, c = 89 # holes are valid
|
|
|
+However, it is then not ordinal anymore, so it is impossible to use these
|
|
|
+enums as an index type for arrays. The procedures inc, dec, succ and pred are
|
|
|
+not available for them either.
|
|
|
+An enum value can be turned into its string representation via the built-in
|
|
|
+stringify operator $. The stringify’s result can be controlled by explicitly
|
|
|
+giving the string values to use:
|
|
|
+ type
|
|
|
+ MyEnum = enum
|
|
|
+ valueA = (0, "my value A"),
|
|
|
+ valueB = "value B",
|
|
|
+ valueC = 2,
|
|
|
+ valueD = (3, "abc")
|
|
|
+As can be seen from the example, it is possible to both specify a field’s
|
|
|
+ordinal value and its string value by using a tuple. It is also possible to only
|
|
|
+84
|
|
|
+specify one of them.
|
|
|
+An enum can be marked with the pure pragma so that its fields are added to a
|
|
|
+special module-specific hidden scope that is only queried as the last attempt.
|
|
|
+Only non-ambiguous symbols are added to this scope. But one can always
|
|
|
+access these via type qualification written as MyEnum.value:
|
|
|
+ type
|
|
|
+ MyEnum {.pure.} = enum
|
|
|
+ valueA, valueB, valueC, valueD, amb
|
|
|
+ OtherEnum {.pure.} = enum
|
|
|
+ valueX, valueY, valueZ, amb
|
|
|
+ echo valueA # MyEnum.valueA
|
|
|
+ echo amb # Error: Unclear whether it's MyEnum.amb or OtherEnum.amb
|
|
|
+ echo MyEnum.amb # OK.
|
|
|
+16.9. Overloadable enum field names
|
|
|
+Enum field names are overloadable much like routines. When an overloaded
|
|
|
+enum field is used, it produces a closed sym choice construct, here written as
|
|
|
+(E|E). During overload resolution the right E is picked, if possible. For
|
|
|
+(array/object...) constructors the right E is picked, comparable to how
|
|
|
+[byte(1), 2, 3] works, one needs to use [T.E, E2, E3]. Ambiguous enum
|
|
|
+fields produce a static error:
|
|
|
+ type
|
|
|
+ E1 = enum
|
|
|
+ value1, value2
|
|
|
+ E2 = enum
|
|
|
+ value1, value2 = 4
|
|
|
+ const
|
|
|
+ lookupTable = [
|
|
|
+ E1.value1: "1",
|
|
|
+ value2: "2"]
|
|
|
+ proc p(e: E1) =
|
|
|
+ # disambiguation in 'case' statements:
|
|
|
+ case e
|
|
|
+ of value1: echo "A"
|
|
|
+ of value2: echo "B"
|
|
|
+ 85
|
|
|
+16.10. String type
|
|
|
+All string literals are of the type string. A string in Nim is very similar to a
|
|
|
+sequence of characters. However, strings in Nim are both zero-terminated
|
|
|
+and have a length field. One can retrieve the length with the builtin len
|
|
|
+procedure; the length never counts the terminating zero.
|
|
|
+The terminating zero cannot be accessed unless the string is converted to the
|
|
|
+cstring type first. The terminating zero assures that this conversion can be
|
|
|
+done in O(1) and without any allocations.
|
|
|
+The assignment operator for strings always copies the string. The & operator
|
|
|
+concatenates strings.
|
|
|
+Most native Nim types support conversion to strings with the special $ proc.
|
|
|
+When calling the echo proc, for example, the built-in stringify operation for
|
|
|
+the parameter is called:
|
|
|
+ echo 3 # calls `$` for `int`
|
|
|
+Whenever a user creates a specialized object, implementation of this
|
|
|
+procedure provides for string representation.
|
|
|
+ type
|
|
|
+ Person = object
|
|
|
+ name: string
|
|
|
+ age: int
|
|
|
+ proc `$`(p: Person): string = # `$` always returns a string
|
|
|
+ result = p.name & " is " &
|
|
|
+ $p.age & # we _need_ the `$` in front of p.age which
|
|
|
+ # is natively an integer to convert it to
|
|
|
+ # a string
|
|
|
+ " years old."
|
|
|
+While $p.name can also be used, the $ operation on a string does nothing. Note
|
|
|
+that we cannot rely on automatic conversion from an int to a string like we
|
|
|
+can for the echo proc.
|
|
|
+Strings are compared by their lexicographical order. All comparison
|
|
|
+operators are available. Strings can be indexed like arrays (lower bound is 0).
|
|
|
+Unlike arrays, they can be used in case statements:
|
|
|
+86
|
|
|
+ case paramStr(i)
|
|
|
+ of "-v": incl(options, optVerbose)
|
|
|
+ of "-h", "-?": incl(options, optHelp)
|
|
|
+ else: write(stdout, "invalid command line option!\n")
|
|
|
+Per convention, all strings are UTF-8 strings, but this is not enforced. For
|
|
|
+example, when reading strings from binary files, they are merely a sequence
|
|
|
+of bytes. The index operation s[i] means the i-th char of s, not the i-th code
|
|
|
+point.
|
|
|
+
|
|
|
+ In the modern programming world, strings are overused
|
|
|
+ heavily. Section 16.27, “Distinct type” contains further advice.
|
|
|
+A reference with the most used operations on strings is available in the
|
|
|
+appendix, under Section B.2, “Strings”.
|
|
|
+16.11. cstring type
|
|
|
+The cstring type meaning compatible string is the native representation of a
|
|
|
+string for the compilation backend. For the C backend the cstring type
|
|
|
+represents a pointer to a zero-terminated char array compatible with the
|
|
|
+type char* in ANSI C. Its primary purpose lies in easy interfacing with C. The
|
|
|
+index operation s[i] means the i-th char of s; however no bounds checking
|
|
|
+for cstring is performed making the index operation unsafe.
|
|
|
+A Nim string is implicitly convertible to cstring for convenience. If a Nim
|
|
|
+string is passed to a C-style variadic proc, it is implicitly converted to cstring
|
|
|
+too:
|
|
|
+ proc printf(formatstr: cstring) {.importc: "printf", varargs,
|
|
|
+ header: "<stdio.h>".}
|
|
|
+ printf("This works %s", "as expected")
|
|
|
+Even though the conversion is implicit, it is not safe: The garbage collector
|
|
|
+does not consider a cstring to be a root and may collect the underlying
|
|
|
+memory.
|
|
|
+A $ proc is defined for cstrings that returns a string. Thus to get a Nim string
|
|
|
+from a cstring:
|
|
|
+ 87
|
|
|
+ var str: string = "Hello!"
|
|
|
+ var cstr: cstring = str
|
|
|
+ var newstr: string = $cstr
|
|
|
+16.12. Structured types
|
|
|
+A variable of a structured type can hold multiple values at the same time.
|
|
|
+Structured types can be nested to unlimited levels. Arrays, sequences, tuples,
|
|
|
+objects, and sets belong to the structured types.
|
|
|
+16.13. Array and sequence types
|
|
|
+Arrays are a homogeneous type, meaning that each element in the array has
|
|
|
+the same type. Arrays always have a fixed length specified as a constant
|
|
|
+expression (except for open arrays). They can be indexed by any ordinal
|
|
|
+type. A parameter A may be an open array, in which case it is indexed by
|
|
|
+integers from 0 to len(A)-1. An array expression may be constructed by the
|
|
|
+array constructor []. The element type of this array expression is inferred
|
|
|
+from the type of the first element. All other elements need to be implicitly
|
|
|
+convertible to this type.
|
|
|
+An array type can be defined using the array[size, T] syntax, or using
|
|
|
+array[lo..hi, T] for arrays that start at an index other than zero.
|
|
|
+Sequences are similar to arrays but of dynamic length which may change
|
|
|
+during runtime (like strings). Sequences are implemented as growable
|
|
|
+arrays, allocating pieces of memory as items are added. A sequence S is
|
|
|
+always indexed by integers from 0 to len(S)-1 and its bounds are checked.
|
|
|
+Sequences can be constructed by the array constructor [] in conjunction with
|
|
|
+the array to sequence operator @. Another way to allocate space for a
|
|
|
+sequence is to call the built-in newSeq procedure.
|
|
|
+A sequence may be passed to a parameter that is of type open array.
|
|
|
+Example:
|
|
|
+ type
|
|
|
+ IntArray = array[0..5, int] # an array that is indexed with 0..5
|
|
|
+ IntSeq = seq[int] # a sequence of integers
|
|
|
+ var
|
|
|
+ x: IntArray
|
|
|
+88
|
|
|
+ y: IntSeq
|
|
|
+ x = [1, 2, 3, 4, 5, 6] # [] is the array constructor
|
|
|
+ y = @[1, 2, 3, 4, 5, 6] # the @ turns the array into a sequence
|
|
|
+ let z = [1.0, 2, 3, 4] # the type of z is array[0..3, float]
|
|
|
+The lower bound of an array or sequence may be received by the built-in
|
|
|
+proc low(), the higher bound by high(). The length may be received by len().
|
|
|
+low() for a sequence or an open array always returns 0, as this is the first
|
|
|
+valid index. One can append elements to a sequence with the add() proc or
|
|
|
+the & operator, and remove (and get) the last element of a sequence with the
|
|
|
+pop() proc.
|
|
|
+The notation x[i] can be used to access the i-th element of x.
|
|
|
+Arrays accesses are bounds checked (statically or at runtime). These checks
|
|
|
+can be disabled via a pragma .push boundChecks:off.
|
|
|
+An array constructor can have explicit indexes for readability:
|
|
|
+ type
|
|
|
+ Values = enum
|
|
|
+ valA, valB, valC
|
|
|
+ const
|
|
|
+ lookupTable = [
|
|
|
+ valA: "A",
|
|
|
+ valB: "B",
|
|
|
+ valC: "C"
|
|
|
+ ]
|
|
|
+If an index is left out, succ(lastIndex) is used as the index value:
|
|
|
+ type
|
|
|
+ Values = enum
|
|
|
+ valA, valB, valC, valD, valE
|
|
|
+ const
|
|
|
+ lookupTable = [
|
|
|
+ valA: "A",
|
|
|
+ "B",
|
|
|
+ valC: "C",
|
|
|
+ "D", "e"
|
|
|
+ ]
|
|
|
+ 89
|
|
|
+A reference with the most used operations on sequences is available in the
|
|
|
+appendix, under Section B.3, “Sequences”.
|
|
|
+16.14. Open arrays
|
|
|
+Often fixed size arrays turn out to be too inflexible; routines should be able
|
|
|
+to deal with arrays of different sizes. The openarray type allows this; it can
|
|
|
+only be used for parameters. Openarrays are always indexed with an int
|
|
|
+starting at position 0. The len, low and high operations are available for open
|
|
|
+arrays too. Any array with a compatible base type can be passed to an
|
|
|
+openarray parameter, the index type does not matter. In addition to arrays,
|
|
|
+sequences can also be passed to an open array parameter.
|
|
|
+The openarray type cannot be nested: multidimensional openarrays are not
|
|
|
+supported because this is seldom needed and cannot be done efficiently.
|
|
|
+ proc testOpenArray(x: openArray[int]) = echo repr(x)
|
|
|
+ testOpenArray([1,2,3]) # array[]
|
|
|
+ testOpenArray(@[1,2,3]) # seq[]
|
|
|
+16.15. Varargs
|
|
|
+A varargs parameter is an openarray parameter that additionally allows to
|
|
|
+pass a variable number of arguments to a procedure. The compiler converts
|
|
|
+the list of arguments to an array implicitly:
|
|
|
+ proc myWriteLn(f: File, a: varargs[string]) =
|
|
|
+ for s in items(a):
|
|
|
+ write(f, s)
|
|
|
+ write(f, "\n")
|
|
|
+ myWriteLn(stdout, "abc", "def", "xyz")
|
|
|
+ # is transformed to:
|
|
|
+ myWriteLn(stdout, ["abc", "def", "xyz"])
|
|
|
+This transformation is only done if the varargs parameter is the last
|
|
|
+parameter in the procedure header. It is also possible to perform type
|
|
|
+conversions in this context:
|
|
|
+90
|
|
|
+ proc myWriteLn(f: File, a: varargs[string, `$`]) =
|
|
|
+ for s in items(a):
|
|
|
+ write(f, s)
|
|
|
+ write(f, "\n")
|
|
|
+ myWriteLn(stdout, 123, "abc", 4.0)
|
|
|
+ # is transformed to:
|
|
|
+ myWriteLn(stdout, [$123, $"def", $4.0])
|
|
|
+In this example $ is applied to any argument that is passed to the parameter
|
|
|
+a. (Note that $ applied to strings is a nop.)
|
|
|
+Note that an explicit array constructor passed to a varargs parameter is not
|
|
|
+wrapped in another implicit array construction:
|
|
|
+ proc takeV[T](a: varargs[T]) = discard
|
|
|
+ takeV([123, 2, 1]) # takeV's T is "int", not "array of int"
|
|
|
+varargs[typed] is treated specially: It matches a variable list of arguments of
|
|
|
+arbitrary type but always constructs an implicit array. This is required so that
|
|
|
+the builtin echo proc does what is expected:
|
|
|
+ proc echo*(x: varargs[typed, `$`]) {...}
|
|
|
+ echo @[1, 2, 3]
|
|
|
+ # prints "@[1, 2, 3]" and not "123"
|
|
|
+16.16. Unchecked arrays
|
|
|
+The UncheckedArray[T] type is a special kind of array where its bounds are not
|
|
|
+checked. This is often useful to implement customized flexibly sized arrays.
|
|
|
+Additionally, an unchecked array is translated into a C array of undetermined
|
|
|
+size:
|
|
|
+ type
|
|
|
+ MySeq = object
|
|
|
+ len, cap: int
|
|
|
+ data: UncheckedArray[int]
|
|
|
+Produces roughly this C code:
|
|
|
+ 91
|
|
|
+ typedef struct {
|
|
|
+ NI len;
|
|
|
+ NI cap;
|
|
|
+ NI data[];
|
|
|
+ } MySeq;
|
|
|
+The base type of the unchecked array may not contain any GC’ed memory
|
|
|
+but this is currently not checked.
|
|
|
+16.17. Tuples and object types
|
|
|
+A variable of a tuple or object type is a heterogeneous storage container. A
|
|
|
+tuple or object defines various named fields of a type. A tuple also defines a
|
|
|
+lexicographic order of the fields. Tuples are meant to be heterogeneous
|
|
|
+storage types with few abstractions. The () syntax can be used to construct
|
|
|
+tuples. The order of the fields in the constructor must match the order of the
|
|
|
+tuple’s definition. Different tuple-types are equivalent if they specify the same
|
|
|
+fields of the same type in the same order. The names of the fields also have to
|
|
|
+be the same.
|
|
|
+The assignment operator for tuples copies each component. The default
|
|
|
+assignment operator for objects copies each component. Overloading of the
|
|
|
+assignment operator is described in Chapter 29, Lifetime-tracking hooks.
|
|
|
+ type
|
|
|
+ Person = tuple[name: string, age: int] # type representing a person:
|
|
|
+ # it consists of a name and an
|
|
|
+ age.
|
|
|
+ var person: Person
|
|
|
+ person = (name: "Peter", age: 30)
|
|
|
+ assert person.name == "Peter"
|
|
|
+ # the same, but less readable:
|
|
|
+ person = ("Peter", 30)
|
|
|
+ assert person[0] == "Peter"
|
|
|
+ assert Person is (string, int)
|
|
|
+ assert (string, int) is Person
|
|
|
+ assert Person isnot tuple[other: string, age: int] # `other` is a different
|
|
|
+ identifier
|
|
|
+A tuple with one unnamed field can be constructed with the parentheses and
|
|
|
+a trailing comma:
|
|
|
+92
|
|
|
+ proc echoUnaryTuple(a: (int,)) =
|
|
|
+ echo a[0]
|
|
|
+ echoUnaryTuple (1,)
|
|
|
+In fact, a trailing comma is allowed for every tuple construction.
|
|
|
+The implementation aligns the fields for the best access performance. The
|
|
|
+alignment is compatible with the way a C compiler does it.
|
|
|
+For consistency with object declarations, tuples in a type section can also be
|
|
|
+defined with indentation instead of []:
|
|
|
+ type
|
|
|
+ Person = tuple # type representing a person
|
|
|
+ name: string # a person consists of a name
|
|
|
+ age: Natural # and an age
|
|
|
+Objects provide many features that tuples do not. Objects provide
|
|
|
+inheritance and the ability to hide fields from other modules. Objects with
|
|
|
+inheritance enabled have information about their type at runtime so that the
|
|
|
+of operator can be used to determine the object’s type. The of operator is
|
|
|
+similar to the instanceof operator in Java.
|
|
|
+ type
|
|
|
+ Person = object of RootObj
|
|
|
+ name*: string # the * means that `name` is accessible
|
|
|
+ # from other modules
|
|
|
+ age: int # no * means that the field is hidden
|
|
|
+ Student = ref object of Person # a student is a person
|
|
|
+ id: int # with an id field
|
|
|
+ var
|
|
|
+ student: Student
|
|
|
+ person: Person
|
|
|
+ assert(student of Student) # is true
|
|
|
+ assert(student of Person) # also true
|
|
|
+Object fields that should be visible from outside the defining module have to
|
|
|
+be marked by *. In contrast to tuples, different object types are never
|
|
|
+equivalent, they are nominal types whereas tuples are structural. Objects that
|
|
|
+have no ancestor are implicitly final and thus have no hidden type
|
|
|
+ 93
|
|
|
+information. One can use the inheritable pragma to introduce new object
|
|
|
+roots apart from system.RootObj.
|
|
|
+ type
|
|
|
+ Person = object # example of a final object
|
|
|
+ name*: string
|
|
|
+ age: int
|
|
|
+ Student = ref object of Person # Error: inheritance only
|
|
|
+ # works with non-final objects
|
|
|
+ id: int
|
|
|
+16.18. fields and fieldPairs iterators
|
|
|
+Nim’s system module provides iterators that can be used to iterate over every
|
|
|
+field of an object or a tuple. fieldPairs yields (key, val) pairs, fields only
|
|
|
+yields the fields' values:
|
|
|
+ proc `$`[T: object](x: T): string = 1
|
|
|
+ result = ""
|
|
|
+ for name, val in fieldPairs(x): 2
|
|
|
+ result.add name
|
|
|
+ result.add ": "
|
|
|
+ result.add $val 3
|
|
|
+ result.add "\n"
|
|
|
+ 1 Possible implementation for how to generically generate the string
|
|
|
+ representation of an object.
|
|
|
+ 2 Iterate over all fields of x.
|
|
|
+ 3 Assume that the type of every field provides a $ operation.
|
|
|
+These iterators do allow for field mutations:
|
|
|
+ proc fromJ[T: object](t: typedesc[T]; j: JsonNode): T = 1
|
|
|
+ result = T()
|
|
|
+ for name, loc in fieldPairs(result):
|
|
|
+ loc = fromJ(typeof(loc), j[name]) 2
|
|
|
+ 1 fromJ loads an object from a JSON tree named j.
|
|
|
+ 2 Store to result.<field>.
|
|
|
+As outlined in the example, fieldPairs and fields can be used as a
|
|
|
+94
|
|
|
+foundation for a serialization library.
|
|
|
+Both fieldPairs and fields can be used to iterate over two objects in tandem:
|
|
|
+ proc `==`[T: object](x, y: T): bool = 1
|
|
|
+ for a, b in fields(x, y):
|
|
|
+ if not (a == b): return false 2
|
|
|
+ return true
|
|
|
+ 1 A possible implementation of an equality operator for two objects of the
|
|
|
+ same type.
|
|
|
+ 2 Assuming that the type of every field provides a == operation.
|
|
|
+16.19. Object construction
|
|
|
+Objects can also be created with an object construction expression that has
|
|
|
+the syntax T(fieldA: valueA, fieldB: valueB, ...) where T is an object type
|
|
|
+or a ref object type:
|
|
|
+ type
|
|
|
+ Student = object
|
|
|
+ name: string
|
|
|
+ age: int
|
|
|
+ PStudent = ref Student
|
|
|
+ var a1 = Student(name: "Anton", age: 5)
|
|
|
+ var a2 = PStudent(name: "Anton", age: 5)
|
|
|
+ # this also works directly:
|
|
|
+ var a3 = (ref Student)(name: "Anton", age: 5)
|
|
|
+ # not all fields need to be mentioned,
|
|
|
+ # and they can be mentioned out of order:
|
|
|
+ var a4 = Student(age: 5)
|
|
|
+Note that, unlike tuples, objects require the field names along with their
|
|
|
+values. For a ref object type system.new is invoked implicitly.
|
|
|
+16.20. Object variants
|
|
|
+Object variants are tagged unions discriminated via an enumerated type
|
|
|
+used for runtime type flexibility, mirroring the concepts of sum types and
|
|
|
+algebraic data types (ADTs) as found in other programming languages.
|
|
|
+An example:
|
|
|
+ 95
|
|
|
+ # This is an example of how an abstract syntax tree could be modelled in Nim
|
|
|
+ type
|
|
|
+ NodeKind = enum # the different node types
|
|
|
+ nkInt, # a leaf with an integer value
|
|
|
+ nkFloat, # a leaf with a float value
|
|
|
+ nkString, # a leaf with a string value
|
|
|
+ nkAdd, # an addition
|
|
|
+ nkSub, # a subtraction
|
|
|
+ nkIf # an if statement
|
|
|
+ Node = ref NodeObj
|
|
|
+ NodeObj = object
|
|
|
+ case kind: NodeKind # the `kind` field is the discriminator
|
|
|
+ of nkInt: intVal: int
|
|
|
+ of nkFloat: floatVal: float
|
|
|
+ of nkString: strVal: string
|
|
|
+ of nkAdd, nkSub:
|
|
|
+ leftOp, rightOp: Node
|
|
|
+ of nkIf:
|
|
|
+ condition, thenPart, elsePart: Node
|
|
|
+ # create a new case object:
|
|
|
+ var n = Node(kind: nkIf, condition: nil)
|
|
|
+ # accessing n.thenPart is valid because the `nkIf` branch is active:
|
|
|
+ n.thenPart = Node(kind: nkFloat, floatVal: 2.0)
|
|
|
+ # the following statement raises an `FieldDefect` exception, because
|
|
|
+ # n.kind's value does not fit and the `nkString` branch is not active:
|
|
|
+ n.strVal = ""
|
|
|
+ # invalid: would change the active object branch:
|
|
|
+ n.kind = nkInt
|
|
|
+ var x = Node(kind: nkAdd, leftOp: Node(kind: nkInt, intVal: 4),
|
|
|
+ rightOp: Node(kind: nkInt, intVal: 2))
|
|
|
+ # valid: does not change the active object branch:
|
|
|
+ x.kind = nkSub
|
|
|
+As can be seen from the example, an advantage to an object hierarchy is that
|
|
|
+no casting between different object types is needed. Yet, access to invalid
|
|
|
+object fields raises an exception.
|
|
|
+The syntax of case in an object declaration follows closely the syntax of the
|
|
|
+case statement: The branches in a case section may be indented too.
|
|
|
+In the example, the kind field is called the discriminator: For safety, its
|
|
|
+address cannot be taken and assignments to it are restricted: The new value
|
|
|
+must not lead to a change of the active object branch. Also, when the fields of
|
|
|
+96
|
|
|
+a particular branch are specified during object construction, the
|
|
|
+corresponding discriminator value must be specified as a constant
|
|
|
+expression.
|
|
|
+Instead of changing the active object branch, replace the old object in
|
|
|
+memory with a new one completely:
|
|
|
+ var x = Node(kind: nkAdd, leftOp: Node(kind: nkInt, intVal: 4),
|
|
|
+ rightOp: Node(kind: nkInt, intVal: 2))
|
|
|
+ # change the node's contents:
|
|
|
+ x[] = NodeObj(kind: nkString, strVal: "abc")
|
|
|
+Starting with version 0.20 system.reset cannot be used anymore to support
|
|
|
+object branch changes as this never was completely memory safe.
|
|
|
+As a special rule, the discriminator kind can also be bounded using a case
|
|
|
+statement. If possible values of the discriminator variable in a case statement
|
|
|
+branch are a subset of discriminator values for the selected object branch,
|
|
|
+the initialization is considered valid. This analysis only works for immutable
|
|
|
+discriminators of an ordinal type and disregards elif branches. For
|
|
|
+discriminator values with a range type, the Nim compiler checks if the entire
|
|
|
+range of possible values for the discriminator value is valid for the chosen
|
|
|
+object branch.
|
|
|
+A small example:
|
|
|
+ let unknownKind = nkSub
|
|
|
+ # invalid: unsafe initialization because
|
|
|
+ # the kind field is not statically known:
|
|
|
+ var y = Node(kind: unknownKind, strVal: "y")
|
|
|
+ var z = Node()
|
|
|
+ case unknownKind
|
|
|
+ of nkAdd, nkSub:
|
|
|
+ # valid: possible values of this branch are a subset of the
|
|
|
+ # nkAdd/nkSub object branch:
|
|
|
+ z = Node(kind: unknownKind, leftOp: Node(), rightOp: Node())
|
|
|
+ else:
|
|
|
+ echo "ignoring: ", unknownKind
|
|
|
+ # also valid, since unknownKindBounded can only contain
|
|
|
+ # the values nkAdd or nkSub
|
|
|
+ let unknownKindBounded = range[nkAdd..nkSub](unknownKind)
|
|
|
+ z = Node(kind: unknownKindBounded, leftOp: Node(), rightOp: Node())
|
|
|
+ 97
|
|
|
+16.21. cast uncheckedAssign
|
|
|
+Via a {.cast(uncheckedAssign).} section some restrictions for case objects can
|
|
|
+be disabled:
|
|
|
+ type
|
|
|
+ TokenKind* = enum
|
|
|
+ strLit, intLit
|
|
|
+ Token = object
|
|
|
+ case kind*: TokenKind
|
|
|
+ of strLit:
|
|
|
+ s*: string
|
|
|
+ of intLit:
|
|
|
+ i*: int64
|
|
|
+ proc passToVar(x: var TokenKind) = discard
|
|
|
+ var t = Token(kind: strLit, s: "abc")
|
|
|
+ {.cast(uncheckedAssign).}:
|
|
|
+ # inside the 'cast' section it is allowed to pass 't.kind'
|
|
|
+ # to a 'var T' parameter:
|
|
|
+ passToVar(t.kind)
|
|
|
+ # inside the 'cast' section it is allowed to set field 's' even though the
|
|
|
+ # constructed 'kind' field has an unknown value:
|
|
|
+ t = Token(kind: t.kind, s: "abc")
|
|
|
+ # inside the 'cast' section it is allowed to assign to the
|
|
|
+ # 't.kind' field directly:
|
|
|
+ t.kind = intLit
|
|
|
+16.22. Set type
|
|
|
+The set type models the mathematical notion of a set. The set’s base type can
|
|
|
+only be an ordinal type of a certain size, namely:
|
|
|
+ • int8-int16
|
|
|
+ • uint8/byte-uint16
|
|
|
+ • char
|
|
|
+ • enum
|
|
|
+or equivalent. For signed integers the set’s base type is defined to be in the
|
|
|
+98
|
|
|
+range 0 .. MaxSetElements-1 where MaxSetElements is currently always 2^16.
|
|
|
+The reason is that sets are implemented as high performance bit vectors.
|
|
|
+Attempting to declare a set with a larger type will result in an error:
|
|
|
+ var s: set[int64] # Error: set is too large
|
|
|
+
|
|
|
+ Nim also offers hash sets (which you need to import with
|
|
|
+ import sets), which have no such restrictions.
|
|
|
+Sets can be constructed via the set constructor: {} is the empty set. The empty
|
|
|
+set is type compatible with any concrete set type. The constructor can also be
|
|
|
+used to include elements (and ranges of elements):
|
|
|
+ type
|
|
|
+ CharSet = set[char]
|
|
|
+ var
|
|
|
+ x: CharSet
|
|
|
+ x = {'a'..'z', '0'..'9'} # This constructs a set that contains the
|
|
|
+ # letters from 'a' to 'z' and the digits
|
|
|
+ # from '0' to '9'
|
|
|
+These operations are supported by sets:
|
|
|
+Table 7. Set operations
|
|
|
+ Operation Meaning
|
|
|
+ A + B union of two sets
|
|
|
+ A * B intersection of two sets
|
|
|
+ A - B difference of two sets (A without B’s elements)
|
|
|
+ A == B set equality
|
|
|
+ A <= B subset relation (A is subset of B or equal to B)
|
|
|
+ A < B strict subset relation (A is a proper subset of B)
|
|
|
+ e in A set membership (A contains element e)
|
|
|
+ e notin A A does not contain element e
|
|
|
+ contains(A, e) A contains element e
|
|
|
+ card(A) the cardinality of A (number of elements in A)
|
|
|
+ 99
|
|
|
+ Operation Meaning
|
|
|
+ incl(A, elem) same as A = A + {elem}
|
|
|
+ excl(A, elem) same as A = A - {elem}
|
|
|
+16.22.1. Bit fields
|
|
|
+Sets are often used to define a type for the flags of a procedure. This is a
|
|
|
+cleaner (and type safe) solution than defining integer constants that have to
|
|
|
+be or'ed together.
|
|
|
+Enum, sets and casting can be used together as in:
|
|
|
+ type
|
|
|
+ MyFlag* {.size: sizeof(cint).} = enum
|
|
|
+ A
|
|
|
+ B
|
|
|
+ C
|
|
|
+ D
|
|
|
+ MyFlags = set[MyFlag]
|
|
|
+ proc toNum(f: MyFlags): int = cast[cint](f)
|
|
|
+ proc toFlags(v: int): MyFlags = cast[MyFlags](v)
|
|
|
+ assert toNum({}) == 0
|
|
|
+ assert toNum({A}) == 1
|
|
|
+ assert toNum({D}) == 8
|
|
|
+ assert toNum({A, C}) == 5
|
|
|
+ assert toFlags(0) == {}
|
|
|
+ assert toFlags(7) == {A, B, C}
|
|
|
+Note how the set turns enum values into powers of 2.
|
|
|
+If using enums and sets with C, use distinct cint.
|
|
|
+For interoperability with C there is also the bitsize pragma.
|
|
|
+16.23. Reference and pointer types
|
|
|
+References (similar to pointers in other programming languages) are a way
|
|
|
+to introduce many-to-one relationships. This means different references can
|
|
|
+point to and modify the same location in memory (also called aliasing).
|
|
|
+100
|
|
|
+Nim distinguishes between traced and untraced references. Untraced
|
|
|
+references are also called pointers. Traced references point to objects of a
|
|
|
+garbage-collected heap, untraced references point to manually allocated
|
|
|
+objects or objects somewhere else in memory. Thus untraced references are
|
|
|
+unsafe. However, for certain low-level operations (accessing the hardware)
|
|
|
+untraced references are unavoidable.
|
|
|
+Traced references are declared with the ref keyword, untraced references
|
|
|
+are declared with the ptr keyword. In general, a ptr T is implicitly
|
|
|
+convertible to the pointer type.
|
|
|
+An empty subscript [] notation can be used to de-refer a reference, the addr
|
|
|
+procedure returns the address of an item. An address is always an untraced
|
|
|
+reference. Thus the usage of addr is an unsafe feature.
|
|
|
+The . (access a tuple/object field operator) and [] (array/string/sequence
|
|
|
+index operator) operators perform implicit dereferencing operations for
|
|
|
+reference types:
|
|
|
+ type
|
|
|
+ Node = ref NodeObj
|
|
|
+ NodeObj = object
|
|
|
+ le, ri: Node
|
|
|
+ data: int
|
|
|
+ var
|
|
|
+ n: Node
|
|
|
+ new(n)
|
|
|
+ n.data = 9
|
|
|
+ # no need to write n[].data; in fact n[].data is highly discouraged!
|
|
|
+In order to simplify structural type checking, recursive tuples are not valid:
|
|
|
+ # invalid recursion
|
|
|
+ type MyTuple = tuple[a: ref MyTuple]
|
|
|
+Likewise T = ref T is an invalid type.
|
|
|
+As a syntactical extension, object types can be anonymous if declared in a
|
|
|
+type section via the ref object or ptr object notations. This feature is useful
|
|
|
+if an object should only gain reference semantics:
|
|
|
+ 101
|
|
|
+ type
|
|
|
+ Node = ref object
|
|
|
+ le, ri: Node
|
|
|
+ data: int
|
|
|
+To allocate a new traced object, the built-in procedure system.new can be
|
|
|
+used.
|
|
|
+To deal with untraced memory, non-built-in procs like system.alloc,
|
|
|
+system.dealloc and system.realloc can be used. But these procs are beyond
|
|
|
+the scope of this document.
|
|
|
+16.24. Nil
|
|
|
+If a reference points to nothing, it has the value nil. nil is the default value
|
|
|
+for all ref and ptr types.
|
|
|
+Dereferencing nil is an unrecoverable fatal runtime error (and not a panic).
|
|
|
+Apart from that, nil is a value like any other - it can be used in assignments
|
|
|
+and comparisons.
|
|
|
+A successful dereferencing operation p[] implies that p is not nil. This can be
|
|
|
+exploited by the implementation to optimize code like:
|
|
|
+ p[].field = 3
|
|
|
+ if p != nil:
|
|
|
+ # if p were nil, `p[]` would have caused a crash already,
|
|
|
+ # so we know `p` is always not nil here.
|
|
|
+ action()
|
|
|
+Into:
|
|
|
+ p[].field = 3
|
|
|
+ action()
|
|
|
+
|
|
|
+ This is not comparable to C’s “undefined behavior” for
|
|
|
+ dereferencing NULL pointers.
|
|
|
+102
|
|
|
+16.25. Procedural type
|
|
|
+A procedural type is internally a pointer to a procedure. nil is an allowed
|
|
|
+value for a variable of a procedural type.
|
|
|
+Examples:
|
|
|
+ proc printItem(x: int) = ...
|
|
|
+ proc forEach(c: proc (x: int) {.cdecl.}) =
|
|
|
+ ...
|
|
|
+ forEach(printItem) # this will NOT compile because
|
|
|
+ # calling conventions differ
|
|
|
+ type
|
|
|
+ OnMouseMove = proc (x, y: int) {.closure.}
|
|
|
+ proc onMouseMove(mouseX, mouseY: int) =
|
|
|
+ # has default calling convention
|
|
|
+ echo "x: ", mouseX, " y: ", mouseY
|
|
|
+ proc setOnMouseMove(mouseMoveEvent: OnMouseMove) = discard
|
|
|
+ # ok, 'onMouseMove' has the default calling convention, which is compatible
|
|
|
+ # to 'closure':
|
|
|
+ setOnMouseMove(onMouseMove)
|
|
|
+16.26. Calling conventions
|
|
|
+A subtle issue with procedural types is that the calling convention of the
|
|
|
+procedure influences the type compatibility: procedural types are only
|
|
|
+compatible if they have the same calling convention. As a special extension, a
|
|
|
+procedure of the calling convention nimcall can be passed to a parameter
|
|
|
+that expects a proc of the calling convention closure.
|
|
|
+The reference implementation supports these calling conventions:
|
|
|
+nimcall
|
|
|
+ is the default convention used for a Nim proc. It is the same as fastcall,
|
|
|
+ but only for C compilers that support fastcall.
|
|
|
+ 103
|
|
|
+closure
|
|
|
+ is the default calling convention for a procedural type that lacks any
|
|
|
+ pragma annotations. It indicates that the procedure has a hidden implicit
|
|
|
+ parameter (an environment). Proc vars that have the calling convention
|
|
|
+ closure take up two machine words: One for the proc pointer and another
|
|
|
+ one for the pointer to implicitly passed environment.
|
|
|
+stdcall
|
|
|
+ This is the stdcall convention as specified by Microsoft. The generated C
|
|
|
+ procedure is declared with the __stdcall keyword.
|
|
|
+cdecl
|
|
|
+ The cdecl convention means that a procedure shall use the same
|
|
|
+ convention as the C compiler. Under Windows the generated C procedure
|
|
|
+ is declared with the __cdecl keyword.
|
|
|
+safecall
|
|
|
+ This is the safecall convention as specified by Microsoft. The generated C
|
|
|
+ procedure is declared with the _safecall keyword. The word _safe refers
|
|
|
+ to the fact that all hardware registers shall be pushed to the hardware
|
|
|
+ stack.
|
|
|
+inline
|
|
|
+ The inline convention means the caller should not call the procedure, but
|
|
|
+ inline its code directly. Note that Nim does not inline, but leaves this to
|
|
|
+ the C compiler; it generates __inline procedures. This is only a hint for a
|
|
|
+ Nim implementation: it may completely ignore it and it may inline
|
|
|
+ procedures that are not marked as inline.
|
|
|
+fastcall
|
|
|
+ Fastcall means different things to different C compilers. One gets
|
|
|
+ whatever the C __fastcall means.
|
|
|
+thiscall
|
|
|
+ This is the thiscall calling convention as specified by Microsoft, used on
|
|
|
+ C++ class member functions on the x86 architecture.
|
|
|
+syscall
|
|
|
+ The syscall convention is the same as __syscall:c: in C. It is used for
|
|
|
+ interrupts.
|
|
|
+104
|
|
|
+noconv
|
|
|
+ The generated C code will not have any explicit calling convention and
|
|
|
+ thus use the C compiler’s default calling convention. This is needed
|
|
|
+ because Nim’s default calling convention for procedures is fastcall to
|
|
|
+ improve speed.
|
|
|
+Most calling conventions exist only for the Windows 32-bit platform.
|
|
|
+The default calling convention is nimcall, unless it is an inner proc (a proc
|
|
|
+inside of a proc). For an inner proc an analysis is performed whether it
|
|
|
+accesses its environment. If it does so, it has the calling convention closure,
|
|
|
+otherwise it has the calling convention nimcall.
|
|
|
+16.27. Distinct type
|
|
|
+A distinct type is a new type derived from a base type that is incompatible
|
|
|
+with its base type. In particular, it is an essential property of a distinct type
|
|
|
+that it does not imply a subtype relation between it and its base type. Explicit
|
|
|
+type conversions from a distinct type to its base type and vice versa are
|
|
|
+allowed.
|
|
|
+In the modern programming world strings are overused heavily: The mere
|
|
|
+fact that JSON, XML, SQL, regular expressions, file paths, etc. have a string
|
|
|
+representation does not imply that you should use string for these things!
|
|
|
+Type safety is compromised when everything is a string. Instead you should
|
|
|
+use different types for different things. As a first step this usually means to
|
|
|
+use a distinct type. The following snippet was extracted from Nim’s standard
|
|
|
+library (db_common.nim):
|
|
|
+ type
|
|
|
+ SqlQuery* = distinct string
|
|
|
+ template sql*(query: string): SqlQuery =
|
|
|
+ SqlQuery(query)
|
|
|
+ iterator rows*(db: DbConn, query: SqlQuery,
|
|
|
+ args: varargs[string, `$`]): Row
|
|
|
+ # usage:
|
|
|
+ for row in rows(sql"SELECT id FROM user WHERE name = ?", "abc"): ...
|
|
|
+ # impossible, prevented at compile-time:
|
|
|
+ for row in rows(sql"SELECT id FROM user WHERE name = ?" & "abc"): ... 1
|
|
|
+ 105
|
|
|
+ 1 Since the SqlQuery is a distinct string there is no & operator for it
|
|
|
+ available.
|
|
|
+16.27.1. borrow annotation
|
|
|
+A borrow annotation can be used in order to borrow an operation from a type
|
|
|
+T to its distinct T equivalent:
|
|
|
+ type
|
|
|
+ Id = distinct int
|
|
|
+ proc `==`(a, b: Id): bool {.borrow.}
|
|
|
+ # Ids can be compared, but have no order so `<=` and `<` are not borrowed.
|
|
|
+16.28. Auto type
|
|
|
+The auto type can only be used for return types and parameters. For return
|
|
|
+types it causes the inference of the type from the routine body:
|
|
|
+ proc returnsInt(): auto = 1984
|
|
|
+For parameters it currently creates implicitly generic routines:
|
|
|
+ proc foo(a, b: auto) = discard
|
|
|
+Is the same as:
|
|
|
+ proc foo[T1, T2](a: T1, b: T2) = discard
|
|
|
+However, later versions of the language might change this to mean "infer the
|
|
|
+parameters' types from the body". Then the above foo would be rejected as
|
|
|
+the parameters' types can not be inferred from an empty discard statement.
|
|
|
+ Usage of auto is discouraged as it has few benefits over spelling
|
|
|
+ out the types explicitly and the severe downside that it makes
|
|
|
+ the code harder to read. Currently Nim’s documentation
|
|
|
+ generator does not translate an auto return type to its inferred
|
|
|
+ type.
|
|
|
+106
|
|
|
+16.29. static[T]
|
|
|
+static is a type modifier. A static parameter must be a constant expression:
|
|
|
+ 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 constant expressions
|
|
|
+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:
|
|
|
+ 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 an 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`
|
|
|
+Please note that static T is just a syntactic convenience for the underlying
|
|
|
+generic type static[T]. The type param can be omitted to obtain the type
|
|
|
+class of all constant expressions. A more specific type class can be created by
|
|
|
+instantiating static with another type class.
|
|
|
+One can force an expression to be evaluated at compile time as a constant
|
|
|
+expression by coercing it to a corresponding static type:
|
|
|
+ import std/math
|
|
|
+ echo static(fac(5)), " ", static[bool](16.isPowerOfTwo)
|
|
|
+ 107
|
|
|
+The Nim compiler should report any failure to evaluate the expression or a
|
|
|
+possible type mismatch error.
|
|
|
+In future versions of the Nim programming language the static metatype
|
|
|
+might not be required at all. It could delay the reporting of an error until the
|
|
|
+generic type is instantiated incorrectly:
|
|
|
+ type
|
|
|
+ Matrix[M, N, T] = array[0..(M*N - 1), T]
|
|
|
+ var a, b: int
|
|
|
+ var m: Matrix[a, b, int] # Error: the array size must be provided at
|
|
|
+ compile-time.
|
|
|
+16.30. 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 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:
|
|
|
+ proc new(T: typedesc): ref T =
|
|
|
+ echo "allocating ", T.name
|
|
|
+ new(result)
|
|
|
+ var n = Node.new
|
|
|
+ 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:
|
|
|
+ proc acceptOnlyTypePairs[T, U](A, B: typedesc[T]; C, D: typedesc[U])
|
|
|
+108
|
|
|
+Once bound, type params can appear in the rest of the proc signature:
|
|
|
+ template declareVariableWithType(T: typedesc, value: T) =
|
|
|
+ var x: T = value
|
|
|
+ declareVariableWithType int, 42
|
|
|
+Overload resolution can be further influenced by constraining the set of
|
|
|
+types that will match the type param:
|
|
|
+ template maxval(T: typedesc[int]): int = high(int)
|
|
|
+ template maxval(T: typedesc[float]): float = Inf
|
|
|
+ var i = int.maxval
|
|
|
+ var f = float.maxval
|
|
|
+ when false:
|
|
|
+ var s = string.maxval # error, maxval is not implemented for string
|
|
|
+ 109
|
|
|
+16.31. typeof
|
|
|
+
|
|
|
+ typeof(x) can for historical reasons also be written as type(x)
|
|
|
+ but type(x) is discouraged.
|
|
|
+One can obtain the type of a given expression by constructing a typeof value
|
|
|
+from it (in many other languages this is known as the typeof operator):
|
|
|
+ var x = 0
|
|
|
+ var y: typeof(x) # y has type int
|
|
|
+If typeof is used to determine the result type of a routine call c(X) (where X
|
|
|
+stands for a possibly empty list of arguments), the interpretation where c is
|
|
|
+an iterator is preferred over the other interpretations, but this behavior can
|
|
|
+be changed by passing typeOfProc as the second argument to typeof:
|
|
|
+ iterator split(s: string): string = discard
|
|
|
+ proc split(s: string): seq[string] = discard
|
|
|
+ # since an iterator is the preferred interpretation, `y` has the type
|
|
|
+ `string`:
|
|
|
+ assert typeof("a b c".split) is string
|
|
|
+ assert typeof("a b c".split, typeOfProc) is seq[string]
|
|
|
+typedesc[T] provides a mechanism for inferring the return type which
|
|
|
+cannot be overloaded. The interaction between typeof, overloading, iterators,
|
|
|
+typedesc[T] and generics allows for idioms that are not obvious to the casual
|
|
|
+user of the language. Here is an example showing how to map JSON data to a
|
|
|
+generic object type.
|
|
|
+ import std / json
|
|
|
+ proc fromJ[T: enum](t: typedesc[T]; j: JsonNode): T {.inline.} = T(j.getInt)
|
|
|
+ 1
|
|
|
+ proc fromJ(t: typedesc[string]; j: JsonNode): string {.inline.} = j.getStr
|
|
|
+ proc fromJ(t: typedesc[bool]; j: JsonNode): bool {.inline.} = j.getBool
|
|
|
+ proc fromJ(t: typedesc[int]; j: JsonNode): int {.inline.} = int(j.getInt)
|
|
|
+ proc fromJ(t: typedesc[float]; j: JsonNode): float {.inline.} = j.getFloat
|
|
|
+ proc fromJ[T: seq](t: typedesc[T]; j: JsonNode): T = 2
|
|
|
+ result = newSeq[typeof(result[0])]()
|
|
|
+ assert j.kind == JArray
|
|
|
+ for elem in items(j):
|
|
|
+ result.add fromJ(typeof(result[0]), elem) 3
|
|
|
+110
|
|
|
+ proc fromJ[T: object](t: typedesc[T]; j: JsonNode): T = 4
|
|
|
+ result = T()
|
|
|
+ assert j.kind == JObject
|
|
|
+ for name, loc in fieldPairs(result): 5
|
|
|
+ if j.hasKey(name):
|
|
|
+ loc = fromJ(typeof(loc), j[name]) 6
|
|
|
+ 1 The fromJ family of procs supports the loading of enums, int, bool, float,
|
|
|
+ string from JSON.
|
|
|
+ 2 A seq can also be loaded from JSON.
|
|
|
+ 3 Depending on the sequence element’s type call the correct overloaded
|
|
|
+ fromJ proc. typeof(result[0]) is passed to the typedesc[T] parameter
|
|
|
+ enabling static dispatching.
|
|
|
+ 4 An object can also be loaded from JSON.
|
|
|
+ 5 Iterate over every field of the object via fieldPairs.
|
|
|
+ 6 fieldPairs allows for the mutation of the object that is iterated over so
|
|
|
+ that loc = ... can be read as result.<field> = ....
|
|
|
+Note that this solution allows to load complex objects which have fields that
|
|
|
+themselves are objects or primitives or sequences thereof. The compiler will
|
|
|
+produce type specialized code with no runtime overhead because the
|
|
|
+dispatching is resolved at compile-time.
|