Meet the record, The Delphi Struct

Feb 9, 2025    m. Mar 23, 2025

An avid Delphi developer would be acquainted with the Class datatype. It is a fundamental part of Object Oriented Programming. But what of structural programming? Enter the record.

Delphi structures

When dealing with collections of related data in Delphi, one often points to classes, a dynamically allocated data type that works the same as a class a language like Java or C++. Delphi, or Pascal, however, still contains structural data types.

The record is a structural data type, much like a struct in C or Rust, which unlike a class, is not allocated dynamically unless needed. If a C struct only contains integers, it will not need to be allocated, the compiler already knows how much memory said integers occupy. The same goes in Rust and I assume so in other structural programming languages, including Delphi.

Let’s take a look at Delphi’s two data storage structures.

Class - OOP

Classes are dynamically allocated data types which are created when their constructors are called. This can be useful in settings where one needs data of unknown size to be stored, such as JSON data, HTTP responses or user information.

To create a class, we call the constructor, this allocates the class in memory. If the constructor is not called, the object will be given a nil value. Trying to call a member function of the class or use a property of the class will lead to an access violation. The class has no value until it has been created.

begin
  var words : TStringList;

  // Call our constructor
  words := TStringList.Create();
  try
    // Manipulate our class
    words.add('Some text');

    // Call a member function of our class
    WriteLn(words.ToString());
  finally 
    // Free any allocated data
    words.free
   end;
end

Record - Struct

Records, much like classes, are collections of data related to a particular entity. Unlike classes, records are not dynamically allocated. They are much like structs in C or Rust. One does not require a constructor to create it, every non-object-class variable within the record will be given a default value.

If a property of a record is a class however, that class must be created using its constructor.

This can be useful when memory usage is not a concern or when creating a small collection that does not contain dynamically allocated data. I find them useful for containing functions, or harbouring enums.

Mind that records are not given constructors or destructors by default, they have no default methods much like C structs. One may create custom constructors however, if they so choose to do so.

begin
  // Hypothetical record StringList
  var words : TStringRec;

  // No access violation
  words.add('Some text');

  // We can use the data as usual
  WriteLn(words.ToString());
end

Declaration

Class declaration

type
TStringList = class(TObject)
  FLINES : String;
  public
    constructor create();
    destructor destroy(); overload;

    procedure Add(S:String);
    function ToString() : String;
  private
    property Lines : String read FLINES write FLINES;
end;

Record declaration

Mind how our record lacks a constructor and a destructor. This is perfectly valid, and we can use this record and its data freely.

type
TStringRec = record
  FLINES : String;
  public
    procedure Add(S:String);
    function ToString() : String;
  private
    property Lines : String read FLINES write FLINES;
end;

Class-like record

Records are also usable as classes. Their declarations are almost identical.

Notice the presence both the constructor method and a destructor method. In this case these are not virtual methods so the destructor is not overloaded. Records do not support inheritance, so there are no virtual methods.

The constructor must have parameters, record constructors without parameters are invalid.

type
TStringList = record
  FLINES : String;
  public
    constructor create( (* params *) );

    // Not a virtual function, so no overload
    destructor destroy();

    procedure Add(S:String);
    function ToString() : String;
  private
    property Lines : String read FLINES write FLINES;
end;

Comparison

Classes and records are two separate types in Delphi and operate differently in their declaration and memory allocation. A record can still be used as a class however, but a class cannot be used as a record, as it requires that the constructor is called before using it.

Records, like C structs, cannot be inherited by other records in declaration. So a record can not have virtual functions or be used to derive another record. This is a case where using a class can be useful and would be preferred. One can also avoid inheritance entirely, opting for a structured data model.

My thoughts

Both classes and records have their own use cases and I believe that they can be used together to deal with dynamic and static memory finely. This is an example of the diverse tool set Delphi provides when dealing with data, being able to act as an object oriented language or a structural language or a mix of both.

Onwards to the next wonder of Delphi.