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.

  1. type
  2. Person = tuple[name: string, age: int] # type representing a person:
  3. # it consists of a name and an age.
  4. var person: Person
  5. person = (name: "Peter", age: 30)
  6. assert person.name == "Peter"
  7. # the same, but less readable:
  8. person = ("Peter", 30)
  9. assert person[0] == "Peter"
  10. assert Person is (string, int)
  11. assert (string, int) is Person
  12. 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:

  1. proc echoUnaryTuple(a: (int,)) =
  2. echo a[0]
  3. 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 the C compiler does it.

For consistency with object declarations, tuples in a type section can also be defined with indentation instead of []:

  1. type
  2. Person = tuple # type representing a person
  3. name: string # a person consists of a name
  4. 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.

  1. type
  2. Person = object of RootObj
  3. name*: string # the * means that `name` is accessible from other modules
  4. age: int # no * means that the field is hidden
  5. Student = ref object of Person # a student is a person
  6. id: int # with an id field
  7. var
  8. student: Student
  9. person: Person
  10. assert(student of Student) # is true
  11. 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 information. One can use the inheritable pragma to introduce new object roots apart from system.RootObj.

  1. type
  2. Person = object # example of a final object
  3. name*: string
  4. age: int
  5. Student = ref object of Person # Error: inheritance only works with non-final objects
  6. id: int

The assignment operator for tuples and objects copies each component. The methods to override this copying behavior are described here.