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.