Records

Records (also called "Structures" or "Structs", in many other programming languages) are a lot like Classes except for two crucial differences:

  • Records are value types, and stored on the stack, while classes are reference types, and stored in global memory.
  • While they can have an ancestry hierarchy (i.e. a record type can decent from and extend another record), they do not support polymorphism, e.g., overriding virtual methods.

When using a record type, the value is stored on the stack (or when defined inside a different type, it is stored inline within the memory space of that type). Assigning a record from one variable to another creates a copy of the record, and making changes to on copy does not affect the other. For this reason, records are usually used to hold a small number of related values.

See Value Types vs. Reference Types) for more on stack- vs heap-baswed types.

Platform Considerations

Records do not support polymorphism, but they can implemented Interfaces on the .NET, Java and Island platforms (but, for technical limitations, not on Cocoa).

On .NET, a StructLayout Aspect can be used to change the size and alignment of a structure, which is useful when used in combination with P/Invoke calls to native platform APIs. Similarly, the FieldOffset Aspect can be used to set the offset of individual fields within the record.

A record type is declared using the record keyword, followed by zero or more member declaratins, and closed off with the end keyword. An optional ancestor and/or a list of one of more interfaces to implement can be provided in parenthesis behind the record keyword:

type
  Color = public record(IColor)
  public
    R, G, B, A: Byte;
  end;

Like all custom types, records can be nested in other types with nested in syntax.

Note: Oxygene records are not to be confused with the record types introduced in C# 9 and Mercury, which add special logic to class (or struct). Oxygene records are the equivalent of a simple struct in C# or Swift, or a Structure in Mercury.

Members

Like Classes, records can contain Type Members, including Fields, Properties and even Methods. Also like in classes, the members can be grouped in Visibility Sections

Packed Records

By default in-memory layout of a record's individual fields is optimized for fast access first, and memory efficiency second. This means that additional padding might be added to make sure Integers and pointers align at 4 or 8-byte boundaries, and the in-memory order of fields might be rearranged, as well.

When a record is marked as packed with the packed directive (or the cross-language Packed Aspect), this will not happen, and the records memory layout will be exactly as specified.

Use packed records when the memory layout matters – for example when reading binary data from disk or the network and accessing it as a record, or when sharing records in-memory with code compiled from non-Elements languages, such as C ort Delphi.

Cocoa and Island Only

Packed records are only supported on Cocoa and the native Island-backed platforms (Windows, Linux, Android NDK and WebAssembly. On .NET and Java, the keyword will be ignored. The Packed aspect is available on the Cocoa and Island platforms only.

Invariants

Records can define Invariants to help ensure a consistent state. Invariants are boolean expressions that will automatically be enforced by the compiler.

Note that invariants can only be effective for non-public fields, as access to public fields would bypass them. This makes invariants less useful for most typical records rthan they are for Classes.

Nested Types

Records can also define Nested Types. Nested types are like regular custom types, except they are considered part of the record and their visibility can be scoped as granular as all class Members.

Refer to the Nested Types topic for more details.

Extension Records

Like Extension Classes, Extension Records can add properties, methods, operators and events to an existing type (but not add anything that requires storage, like fields, events or stored properties). These become available to callers if this type is in scope for the caller. The first type in the ancestor defines which type gets extended; optional interfaces can be used to add/implement as interfaces allowing the type to be compatible with that interface.

Read more about Extension Records here.

Type Visibility

The visibility of a record type can be controlled by applying a Visibility Modifier on the declaration. The default visibility is assembly.

Other Modifiers

A number of other Type Modifiers can be applied to records:

  • extension Makes this an extension record; see Extensions.
  • mapped Makes this a mapped record; see Mapped Types.
  • partial Partial can be used to spread a type over several files in the same project. All parts must have this modifier then.
  • readonly Makes this record readonly. All fields in it will be readonly and can only be set by a constructor and not modified afterwards.
  • static Static records are records with only static members.
  • packed Packed records do not align their members by round offsets. Ignored on .NET and Java

See Also