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.