Records
Records cannot be compared with = unless you overload the operator (Chapter 31).
15.1 Declaring and using records
Records cannot be compared with = unless you overload the operator (Chapter 31). Use CompareMem only for records without managed fields or padding.
type
TDate = record
Day, Month, Year: Integer;
end;
TPerson = record
Name: string;
Born: TDate;
Email: string;
end;
var
P, Q: TPerson;
begin
P.Name := 'Ada Lovelace';
P.Born.Day := 10;
P.Born.Month := 12;
P.Born.Year := 1815;
Q := P; // records are value types: full copy
Q.Name := 'Copy';
Writeln(P.Name); // Ada Lovelace
end.15.2 Record constants and initialization
const
Origin: TDate = (Day: 1; Month: 1; Year: 1970);
var
D: TDate;
begin
D := Default(TDate); // zero-initialize any type (FPC 3.0+, Delphi 2009+)
end.15.3 The with statement
with saves typing but is discouraged: it makes it unclear which identifiers belong to the record, and adding a field later can silently change meaning. Use a short local variable or pointer instead.
with P.Born do
begin
Day := 1;
Month := 1;
Year := 2000;
end;15.4 Variant records
A variant part lets different fields share the same memory, selected by a tag:
The variant part must be last; managed types (string, dynamic arrays, interfaces) are not allowed in it.
The compiler does not check that you read the variant matching the tag.
Untagged variants (case Integer of) are used for type punning, e.g. viewing a Cardinal as four bytes.
type
TShapeKind = (skCircle, skRectangle, skTriangle);
TShape = record
X, Y: Double; // common fields
case Kind: TShapeKind of // tag field
skCircle: (Radius: Double);
skRectangle: (Width, Height: Double);
skTriangle: (A, B, C: Double);
end;
function Area(const S: TShape): Double;
var
H: Double; // half perimeter for Heron's formula
begin
case S.Kind of
skCircle: Result := Pi * Sqr(S.Radius);
skRectangle: Result := S.Width * S.Height;
skTriangle:
begin
H := (S.A + S.B + S.C) / 2;
Result := Sqrt(H * (H - S.A) * (H - S.B) * (H - S.C));
end;
else
Result := 0;
end;
end;15.5 Packed records and alignment
Use packed (or {$PACKRECORDS n} in FPC, {$ALIGN n} / {$A n} in Delphi) to match binary file formats and C structures exactly. Unaligned access is slower and may fault on some CPUs. For C interop, FPC's {$PACKRECORDS C} follows the C compiler's layout rules.
type
TNormal = record
B: Byte;
I: Integer;
end;
TPacked = packed record
B: Byte;
I: Integer;
end;
begin
Writeln(SizeOf(TNormal)); // 8 — padding aligns I to 4 bytes
Writeln(SizeOf(TPacked)); // 5 — no padding
end.15.6 Advanced records (methods)
Records can have methods, properties, constructors, and operators. In FPC objfpc mode, enable them with {$modeswitch advancedrecords} (on by default in delphi mode):
Advanced records stay value types with no inheritance or virtual methods — ideal for small, fast types like vectors, money, and date ranges.
program AdvRecords;
{$mode objfpc}{$H+}
{$modeswitch advancedrecords}
type
TVec2 = record
private
function GetLength: Double;
public
X, Y: Double;
class function Make(AX, AY: Double): TVec2; static;
procedure Scale(Factor: Double);
function ToString: string;
property Length: Double read GetLength;
end;
class function TVec2.Make(AX, AY: Double): TVec2;
begin
Result.X := AX;
Result.Y := AY;
end;
function TVec2.GetLength: Double;
begin
Result := Sqrt(X * X + Y * Y);
end;
procedure TVec2.Scale(Factor: Double);
begin
X := X * Factor;
Y := Y * Factor;
end;
function TVec2.ToString: string;
begin
WriteStr(Result, '(', X:0:2, ', ', Y:0:2, ')'); // FPC; Delphi: Format('(%.2f, %.2f)', [X, Y])
end;
var
V: TVec2;
begin
V := TVec2.Make(3, 4);
Writeln(V.Length:0:1); // 5.0
V.Scale(2);
Writeln(V.ToString); // (6.00, 8.00)
end.