Value Types vs. Reference Types

All Elements languages and platforms divide the type system into two fundamentally different kind of types* *Value Types and Referende Types.

Value Types

A Value Type exists locally in the place it is declared, and holds its value right inside its memory space. Int32 for example, is a 32-bit (four byte) Integer type. It takes up four bytes of memory, and those four bytes contain the number held by the integer, anywhere from 00000000 00000000 00000000 00000000 to FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF. When it is passed somewhere else – assigned to a second variable, passed to a method, etc., its value is copied and , in essence, two (or more) copies of the value now exist. Changing the value of the first integer does not affect the original value:

var x := 5;
var y := x;
y := 6; // x is still 5
var x = 5;
var y = x;
y = 6; // x is still 5
var x = 5
var y = x
y = 6  // x is still 5
var x = 5;
var y = x;
y = 6; // x is still 5
var x = 5
var y = x
y = 6; // x is still 5
Dim x = 5
Dim y = x
y = 6 ' x is still 5

The following types are Value Types:

  • Simple numeric types (Integers, Floats, Chars)
  • Enums and related types (Flags and Sets, in Oxygene)
  • Structs (or Records in Oxygene, not to be confused with unaptly-named Records in C# 9.0)

Simple Types and Enums consist of a single value, the size determiend by its type (e.g. an Int32 takes up 4 bytes; a Double takes up 8 bytes, a (UTF-16) Char takes two bytes.

Structs/Records combine one or more fields, and take up as much space as the combined set of fields needs (plus additional padding bytes used to align values on even addresses, for performance reasons).

As mentioned above, a value type takes up space right where it is declared. For a local variable, sufficient space will be allocated on the stack, value types passed as parameters are copied to the strack for the called function, or passed via registers (depending on size and calling convention). value types declared as field in a class or a struct/record take up as muc space as required in the overall memory for that class opr struct.

When a value type variable or field comes into existence (standalone, or as part of a larger class or struct), memory used for it is initialized to zero (00000000), so code can rely on value types having a value of default<T>, the default value for its type.

Reference Types

By contrast, the data for Referende Types is not stored locally, but in memory allocated elsewhere (usually, but not always, on the global memory heap for the process).

For the purpose of this topic, it is best to think of Refenrence Types as what they are underneath: they are really a value type of the type Pointer, that holds the address to the actual object.

When a reference type variable or field comes into existence (standalone, or as part of a larger class or struct), the memory used for it pointer is initialized to zero (00000000), same as for value types. This results in no actual object being instatiated yet, instead the referende is nil (Oxygene or Swift), or null (C#, Java or Mercury).

In order or work with a referene type, an instance need to be obtained. Either a new instance needs to be created and assigned to the variable or field, or an existing reference needs to be copied over.

When a reference type is passed somewhere else – assigned to a second variable, passed to a method, etc., the value that is is copied is the address of the object being passed or assigned, so afterwards, two places refer to the same object in memory. So changing the value of the object via the new reference will change the original object, and thus affect what is seen by the original reference:

var x := new SomeClass(5);
var y := x;
y.StoredValue := 6; // x.StoredValue now also is 6
var x = new SomeClass(5);
var y = x;
y.StoredValue = 6; // x.StoredValue now also is 6
var x = SomeClass(5)
var y = x
y.StoredValue = 6 // x.StoredValue now also is 6
var x = new SomeClass(5);
var y = x;
y.StoredValue = 6; // x.StoredValue now also is 6
Dim x = New SomeClass(5)
Dim y = x
y.StoredValue = 6 ' x.StoredValue now also is 6

The following types are Reference Types:

  • Any type declared as class in any of the languages (aside from Go, which does not support classes), as well as record in C# 9.0 ir Mercury (not to be confused with records in Oxygene, which are structs and thus value types).
  • Any interface (or protocol, in Swift parlance) referende to a value type.

Reference Types Are Pointers

Alluded to above, Reference Types really are a modern language abstraction on pointers, and in many ways their behavior is best understood they are thought as a Value Type containing an address:

If you pass a value type to a method, the method gets its own copy of the value. Any changes the method makes to its copy of the value dont affect the original. If instead you pass a reference type to a method, the method gets its own copy of the address to that type. If the method changes the address (say, assignes a new object to the parameter), it would not affect the called. But if the method just uses the address to access and changes the original object itself, then the none object known to both callee and caller is affected.

Similarly, if a value type is passed "by reference" (var in Oxygene, ref in C#, Swift or Java, ByRef in Mercury), caller and callee share the same value, and changes propagate back. if a reference type is passed "by reference", the pointer is shared, and if the callee changes its address, the callee wil also see the new address and the new object.

If a value type is stored in a struct (or indeed a class), then creating a copy of that struct will create a second, independent copy of that value. If a reference type is stored in a struct, then the copy will get a copy of the address to the same object. It can choose to change the address to a new object without affecting the original struct, but if it just uses the address to change the object, the one shared object is affected.

Memory Management

For all Elements languages and platforms, the system take scare of memory management of heap-allocated reference types for you. For Cocoa objects, it uses Automatic Referene Counting (ARC) to maintain track of how many references to an object exist, and to free the memory when the last reference is dropped. All other platforms and object models use Garbage Collection (GC) to clean up and reclaim unused memory after objects are no longer referenced.

The ARC vs. GC topic covers this in more detail.

Nullability

By the virtue of how they are stored in memory it makes sense that Reference Types allow null values by default (it is in their very nature that they are null until properly initialized), while Value Types don't support null values by default (they exist in place as a value, so they can, at best, be zero).

Elements provides, across all languages, to use value type variables/fields that can be null, and reference type variables/fields that are not allowed to be null. In the former case, this comes with some additional memory and processing overhead to store the additional "is null or not" information; in the latter case it merely comes doen to additional compiler checks and enforcements. The Nullability topic covers nullable and non-nullable types in more detail.

See Also