Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Language Atlas (mini-courses for more languages)  Module 4 – Object Pascal with Free Pascal (Delphi-compatible)

Pascal classes, generics and exceptions

Write Object Pascal classes with properties, release every object with try and finally, raise your own exceptions, and use generics in objfpc and delphi mode.

  • Intermediate
  • 15 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7 and fpc 3.2.2
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Define classes with properties, constructors and destructors, and free every object with try ... finally
  • Find a leaked object with the heaptrc unit and fix the code that leaked it
  • Handle bad input by raising exception classes of your own and catching them with on E: class do
  • Use Generics.Collections in objfpc and delphi mode and write a generic container of your own
  • Explain how COM interfaces count references, and why Free must not be mixed with them

Before you start

What you’ll learn · Before you start

What you will learn

  • Define classes with properties, constructors and destructors, and free every object with try ... finally
  • Find a leaked object with the heaptrc unit and fix the code that leaked it
  • Handle bad input by raising exception classes of your own and catching them with on E: class do
  • Use Generics.Collections in objfpc and delphi mode and write a generic container of your own
  • Explain how COM interfaces count references, and why Free must not be mixed with them

Before you start

On this page

leak.pas creates one student object, prints it and ends, and Free Pascal’s heap report then says 1 unfreed memory blocks : 24: no garbage collector frees a class instance, so the 24 bytes stay allocated until the program exits. In a service that handles a million records, the same line leaks at least 24 megabytes.

This lesson covers the habits that keep Object Pascal objects in order: who frees each object and how try ... finally makes sure of it, properties that check a value on its way in, exception classes of your own and handlers that choose by class, generic containers in both dialects, with a ring buffer you can step through, and interfaces that count their references and free objects themselves.

A class instance lives on the heap until you free it

The Reference Guide puts it plainly: a class is a pointer to an object on the heap. Declaring s: TStudent reserves room for the pointer only; TStudent.Create allocates the object and runs the constructor, and nothing frees it until you call Free, which calls the destructor Destroy if the reference is not nil. For a Python or Java programmer that is the big change, since there is no garbage collector for class instances. For a C programmer it is malloc and free with a constructor and a destructor attached.

The three programs use one small unit, Student, whose destructor prints a line, so you can see each object go:

The Student unit and three programs that use it

ownership/student.pas

unit Student;
{ A student with a name and a mark. The class is used by every program of this folder. }
{$mode objfpc}{$H+}

interface

type
  TStudent = class
  private
    FName: string;
    FMarks: Integer;
  public
    constructor Create(const AName: string; AMarks: Integer);
    destructor Destroy; override;
    property Name: string read FName;
    property Marks: Integer read FMarks;
  end;

implementation

constructor TStudent.Create(const AName: string; AMarks: Integer);
begin
  inherited Create;
  FName := AName;
  FMarks := AMarks;
end;

destructor TStudent.Destroy;
begin
  WriteLn('  freeing ', FName);
  inherited Destroy;
end;

end.

ownership/leak.pas

program Leak;
{ Creates a student and never frees it. Built with -gh, the heaptrc unit reports the block left over. }
{$mode objfpc}{$H+}

uses
  Student;

var
  s: TStudent;
begin
  s := TStudent.Create('Asha', 91);
  WriteLn(s.Name, ': ', s.Marks);
end.

ownership/fixed.pas

program Fixed;
{ The same student, freed in a finally block, which runs however the block in try is left. }
{$mode objfpc}{$H+}

uses
  Student;

var
  s: TStudent;
begin
  s := TStudent.Create('Asha', 91);
  try
    WriteLn(s.Name, ': ', s.Marks);
  finally
    s.Free;
  end;
end.

ownership/owned.pas

program Owned;
{ A list that owns its students: freeing the list frees every student in it. }
{$mode objfpc}{$H+}

uses
  Generics.Collections, Student;

type
  TStudentList = specialize TObjectList<TStudent>;

var
  students: TStudentList;
  s: TStudent;
begin
  students := TStudentList.Create(True);   { True: the list owns its items }
  try
    students.Add(TStudent.Create('Asha', 91));
    students.Add(TStudent.Create('Ravi', 58));
    students.Add(TStudent.Create('Meera', 74));
    for s in students do
      WriteLn(s.Name, ': ', s.Marks);
  finally
    students.Free;
  end;
end.

Each is built with -gh, which adds the heaptrc: A unit of Free Pascal's run-time library that counts every memory block a program allocates and frees, and prints a report when the program ends. Building with -gh adds it; a block still allocated at the end is a leak. unit; its documentation says it keeps track of every allocation and, when the program exits, lists the blocks that were allocated but not freed:

A leaked student, the same student freed, and a list that owns its students Python · ownership_session.py
from session import run

# Ownership: a student that is never freed, the heap report of the heaptrc unit (-gh), the same program with
# try ... finally, and a list that owns its students. -vn- hides the notes that Generics.Collections' own code
# produces when it is compiled.
run("""
cd ownership
fpc -l- -vi- -gh leak.pas
./leak
fpc -l- -vi- -gh fixed.pas
./fixed
fpc -l- -vi- -vn- -gh owned.pas
./owned
""")

Output

$ cd ownership
$ fpc -l- -vi- -gh leak.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./leak
Asha: 91
Heap dump by heaptrc unit of ./leak
1 memory blocks allocated : 24/24
0 memory blocks freed     : 0/0
1 unfreed memory blocks : 24
True heap size : 327680 (32 used in System startup)
True free heap : 327392
Should be : 327432
Call trace for block $… size 24
  $…
  $…
$ fpc -l- -vi- -gh fixed.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./fixed
Asha: 91
  freeing Asha
Heap dump by heaptrc unit of ./fixed
1 memory blocks allocated : 24/24
1 memory blocks freed     : 24/24
0 unfreed memory blocks : 0
True heap size : 327680 (32 used in System startup)
True free heap : 327648
$ fpc -l- -vi- -vn- -gh owned.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./owned
Asha: 91
Ravi: 58
Meera: 74
  freeing Asha
  freeing Ravi
  freeing Meera
Heap dump by heaptrc unit of ./owned
29 memory blocks allocated : 3400/3408
29 memory blocks freed     : 3400/3408
0 unfreed memory blocks : 0
True heap size : 622592 (32 used in System startup)
True free heap : 622560

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ownership_session.py

leak.pas allocated one block of 24 bytes and freed none: 1 unfreed memory blocks : 24, followed by a call trace of memory addresses, shown as $… because they change on every run. fixed.pas frees the student in a finally block, and its report says 0 unfreed memory blocks : 0. owned.pas hands its three students to a list created with TObjectList.Create(True), which owns them: freeing the list freed all three, and every one of the 29 blocks the program allocated came back. Step through where Free runs in fixed.pas, on the normal path and when an exception leaves the try block early:

Interactive · stage by stage Where Free runs in try ... finally Step through fixed.pas on its normal path, then the same block when the code inside try raises an exception.
Where Free runs in try ... finally, step 6 of 6. Showing TStudent.Create, The code inside try, finally: s.Free, The statement after end, A handler further up. In focus: onward, A handler further up.an exceptionstill raisedTStudent.Createone block of 24 bytesThe code inside tryprints Asha: 91finally: s.Freeruns on every way outThe statement after endnormal path onlyA handler further upexcept in a caller

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Create first. s := TStudent.Create('Asha', 91) allocates the object on the heap, one block of 24 bytes in the heaptrc report. It comes before try: if the constructor fails, there is no object yet, and nothing to free.
  2. Use it inside try. The code between try and finally uses the object; here it prints Asha: 91.
  3. finally frees it. When the code inside try ends, the finally block runs. s.Free calls the destructor, which printed freeing Asha, and heaptrc then reported 0 unfreed memory blocks.
  4. On it goes. After end the program carries on with the next statement. leak.pas, which had no finally and no Free, ended with 1 unfreed block of 24 bytes instead.
  5. An exception inside try. If the code inside try raises an exception, the rest of it is skipped and control goes straight to finally, so the object is freed on this path too.
  6. The exception carries on. finally does not handle the exception. After it runs, the exception goes on to the nearest handler: in parse_marks.pas, finally printed done with "191" before the handler printed bad mark.

Create inside try

Writing try s := TStudent.Create(…); … finally s.Free; end; looks tidier but frees an unassigned variable when the constructor itself raises an exception. The Reference Guide says a constructor that fails calls the destructor automatically, so there is nothing left to free: write s := TStudent.Create(…) first, then try.

Checkpoint

Check: Who frees an object, and try ... finally

  1. Question 1 of 2 leak.pas was built with -gh. What did the heap report say at the end?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 1 unfreed memory blocks : 24, the student that was never freed

    A class instance stays allocated until Free is called. Building with -gh adds the heaptrc unit, which lists every block still allocated at the end; the call trace's addresses change on every run.

  2. Question 2 of 2 owned.pas adds three students to TStudentList.Create(True) and frees only the list. What happens to the students?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The list frees them: True makes it own its items

    One owner per object: here the list owns the students, so code that adds them must not free them as well.

A property puts an API in front of a field

A property: A class member that reads and writes like a field but goes through the accessors it names, as in property Marks: Integer read FMarks write SetMarks. A property without a write accessor is read-only, and the compiler refuses an assignment to it. reads and writes like a field, but each access goes through what it names. property Marks: Integer read FMarks write SetMarks reads the private field FMarks directly and sends every assignment through the method SetMarks, which can refuse a bad value. The caller writes s.Marks := 91 either way, so a field can become a checked property later without changing a single call site. property Name: string read FName, with no write, is read-only:

marked.pas, a property that checks what is written to it PAS · props/marked.pas
program Marked;
{ A property gives a field an API: reading Marks reads the field, writing it calls SetMarks, which checks the value.
  The call sites look like plain field access. }
{$mode objfpc}{$H+}

uses
  SysUtils;

type
  EBadMarks = class(Exception);

  TStudent = class
  private
    FName: string;
    FMarks: Integer;
    procedure SetMarks(Value: Integer);
  public
    constructor Create(const AName: string);
    property Name: string read FName;
    property Marks: Integer read FMarks write SetMarks;
  end;

constructor TStudent.Create(const AName: string);
begin
  inherited Create;
  FName := AName;
end;

procedure TStudent.SetMarks(Value: Integer);
begin
  if (Value < 0) or (Value > 100) then
    raise EBadMarks.CreateFmt('%s: %d is not a mark from 0 to 100', [FName, Value]);
  FMarks := Value;
end;

var
  s: TStudent;
begin
  s := TStudent.Create('Asha');
  try
    s.Marks := 91;
    WriteLn(s.Name, ' has ', s.Marks);
    try
      s.Marks := 191;
    except
      on E: EBadMarks do WriteLn(E.ClassName, ': ', E.Message);
    end;
    WriteLn(s.Name, ' still has ', s.Marks);
  finally
    s.Free;
  end;
end.
A checked property, and an assignment to a read-only one Python · props_session.py
from session import run

# A property that checks the value written to it, and a read-only property that the compiler protects.
run("""
cd props
fpc -l- -vi- marked.pas
./marked
!fpc -l- -vi- readonly.pas
""")

Output

$ cd props
$ fpc -l- -vi- marked.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./marked
Asha has 91
EBadMarks: Asha: 191 is not a mark from 0 to 100
Asha still has 91
$ fpc -l- -vi- readonly.pas
readonly.pas(17,10) Error: No member is provided to access property
readonly.pas(20) Fatal: There were 1 errors compiling module, stopping
Fatal: Compilation aborted
Error: …/bin/ppca64 returned an error exitcode
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 props_session.py

s.Marks := 191 became a call to SetMarks(191), which raised EBadMarks with the message Asha: 191 is not a mark from 0 to 100, and the student still had 91 afterwards. The assignment to Name in readonly.pas did not compile: No member is provided to access property.

You write What runs
x := s.Marks reads the field FMarks
s.Marks := 191 calls SetMarks(191), which may raise
s.Name := 'Ravi' nothing: the compiler refuses it

Python’s @property with a setter does the same job without changing how the call reads. In Java a getter and a setter do it, and every caller then names the method.

Checkpoint

Check: Properties and their accessors

  1. Question 1 of 2 s.Marks is declared as property Marks: Integer read FMarks write SetMarks. What does s.Marks := 191 do?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It calls SetMarks(191), which can refuse the value; in the run it raised EBadMarks

    A property makes the field private and the access controlled, without changing how callers write it.

  2. Question 2 of 2 property Name: string read FName has no write part. What did the compiler say to s.Name := 'Ravi'?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No member is provided to access property

    Read-only properties are how a Pascal class publishes values it alone may change.

An exception travels to the first handler whose class matches

parse_marks.pas turns four pieces of text into marks. It declares a small hierarchy of its own, EMarksError = class(Exception) with two children, EBadMarks and EMissingMarks, and it also meets EConvertError, which SysUtils’ StrToInt raises for text that is not a number. Each input goes through a finally block on its way to the handlers:

parse_marks.pas, exceptions of its own and of SysUtils PAS · errors/parse_marks.pas
program ParseMarks;
{ Exceptions: a small hierarchy of your own, handlers that choose by class, a finally block that runs on the way out,
  and at the end one exception that nobody handles. }
{$mode objfpc}{$H+}{$R+}

uses
  SysUtils;

type
  EMarksError = class(Exception);          { every error of the marks report }
  EBadMarks = class(EMarksError);          { a mark outside 0..100 }
  EMissingMarks = class(EMarksError);      { no mark at all }
  TMarks = 0..100;

const
  Inputs: array[1..4] of string = ('91', '', '191', 'ninety');

function ToMarks(const Text: string): TMarks;
var
  n: Integer;
begin
  if Text = '' then
    raise EMissingMarks.Create('no mark given');
  n := StrToInt(Text);                     { raises EConvertError for 'ninety' }
  if (n < 0) or (n > 100) then
    raise EBadMarks.CreateFmt('%d is not a mark from 0 to 100', [n]);
  Result := n;
end;

procedure Report(const Text: string);
var
  m: TMarks;
begin
  try
    m := ToMarks(Text);
    WriteLn('"', Text, '" -> ', m);
  finally
    WriteLn('  finally: done with "', Text, '"');
  end;
end;

var
  t: string;
begin
  for t in Inputs do
    try
      Report(t);
    except
      on E: EBadMarks do WriteLn('  bad mark: ', E.Message);
      on E: EMarksError do WriteLn('  ', E.ClassName, ': ', E.Message);
      on E: EConvertError do WriteLn('  not a number: ', E.Message);
    end;
  WriteLn('now one that nobody handles:');
  Report('101');
end.
Four inputs, three handlers, finally blocks, and one exception that nobody handles Python · errors_session.py
from session import run

# Four inputs through a parser that raises exceptions of its own and one of SysUtils, handlers that choose by class,
# finally blocks, and an exception that no handler catches.
run("""
cd errors
fpc -l- -vi- parse_marks.pas
!./parse_marks
""")

Output

$ cd errors
$ fpc -l- -vi- parse_marks.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./parse_marks
"91" -> 91
  finally: done with "91"
  finally: done with ""
  EMissingMarks: no mark given
  finally: done with "191"
  bad mark: 191 is not a mark from 0 to 100
  finally: done with "ninety"
  not a number: "ninety" is an invalid integer
now one that nobody handles:
  finally: done with "101"
An unhandled exception occurred at $…:
EBadMarks: 101 is not a mark from 0 to 100
  $…
  $…
[exit status 217]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 errors_session.py

Input Raised by Exception Handler that ran
'91' nothing none none: it printed 91
'' ToMarks EMissingMarks on E: EMarksError
'191' ToMarks EBadMarks on E: EBadMarks
'ninety' StrToInt EConvertError on E: EConvertError

The Reference Guide gives the rule: the handlers are tried in order, a handler matches an exception of its own class or of a class derived from it, and the first match is used. So EBadMarks must come before its parent EMarksError, or the parent’s handler would take it. Every finally: done line came before the handler’s line, because a finally block runs as the exception leaves its try and then lets it carry on. The last call, with '101', has no handler around it: the run printed An unhandled exception occurred, the class and the message, and stopped with exit status 217, which the User’s Guide lists as an unhandled exception caught by SysUtils’ default handler.

One base class per program

Derive every exception of your own from one class, like EMarksError. A caller can then catch all of them with one handler, while bugs such as EAccessViolation still reach the top and stop the program.

Checkpoint

Check: Raising and handling exceptions

  1. Question 1 of 2 Handlers are written on E: EMarksError do ... first and on E: EBadMarks do ... second. EBadMarks derives from EMarksError. Which handler runs for an EBadMarks?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The EMarksError handler, so the EBadMarks handler never runs

    The Reference Guide says the first matching type is used, a match being the class itself or a parent of it. parse_marks.pas lists EBadMarks before EMarksError for this reason.

  2. Question 2 of 2 Report('191') raises EBadMarks inside a try ... finally, and its caller has the handler. In which order did the two lines print?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: finally: done with "191" first, then bad mark: 191 is not a mark from 0 to 100

    try ... finally cleans up and passes the exception on; try ... except decides what to do about it.

Generics: one container for every type

Generics.Collections gives Object Pascal the containers Python and Java programmers expect: TList<T>, TDictionary<TKey, TValue>, TObjectList<T> and more. A specialization: A type made from a generic by filling in its type parameters, such as TList<Integer> from TList<T>. objfpc mode writes it with the keyword specialize; delphi mode writes the type name alone. such as TList<Integer> is the generic with its type filled in; in objfpc mode every specialization is written with specialize, as the first lesson of this module showed. collections.pas sorts a list of marks and looks names up in a dictionary:

collections.pas, Generics.Collections in objfpc mode PAS · generics/collections.pas
program Collections;
{ Generics.Collections in objfpc mode: a list of marks and a dictionary from names to marks. }
{$mode objfpc}{$H+}

uses
  Generics.Collections;

type
  TMarkList = specialize TList<Integer>;
  TMarkBook = specialize TDictionary<string, Integer>;

var
  marks: TMarkList;
  book: TMarkBook;
  m, best: Integer;
begin
  marks := TMarkList.Create;
  book := TMarkBook.Create;
  try
    marks.AddRange([72, 58, 91, 66]);
    marks.Sort;
    Write('sorted:');
    for m in marks do
      Write(' ', m);
    WriteLn;
    book.Add('Asha', 91);
    book.Add('Ravi', 58);
    if book.TryGetValue('Asha', best) then
      WriteLn('Asha: ', best);
    WriteLn('Meera in the book: ', book.ContainsKey('Meera'), ', entries: ', book.Count);
  finally
    book.Free;
    marks.Free;
  end;
end.

You can also write generics yourself. TRingBuffer<T> keeps up to a fixed number of items in a ring of slots: Push writes at rear, Pop reads at front, both move on one slot and wrap from the last slot to slot 0, and two exception classes report a full and an empty ring. The unit is written in delphi mode, and an objfpc program uses it too, because each unit is compiled in its own mode:

The generic ring buffer and two programs that use it

generics/ringbuffer.pas

unit RingBuffer;
{ A generic ring buffer of fixed capacity, written in delphi mode. Push adds at rear and Pop takes from front; both
  wrap round to slot 0 after the last slot, so no item ever moves. }
{$mode delphi}

interface

uses
  SysUtils;

type
  EBufferFull = class(Exception);
  EBufferEmpty = class(Exception);

  TRingBuffer<T> = class
  private
    FItems: array of T;
    FFront, FRear, FCount: Integer;
    function GetCapacity: Integer;
  public
    constructor Create(ACapacity: Integer);
    procedure Push(const Item: T);
    function Pop: T;
    property Count: Integer read FCount;
    property Capacity: Integer read GetCapacity;
    property Front: Integer read FFront;
    property Rear: Integer read FRear;
  end;

implementation

constructor TRingBuffer<T>.Create(ACapacity: Integer);
begin
  inherited Create;
  SetLength(FItems, ACapacity);
end;

function TRingBuffer<T>.GetCapacity: Integer;
begin
  Result := Length(FItems);
end;

procedure TRingBuffer<T>.Push(const Item: T);
begin
  if FCount = Capacity then
    raise EBufferFull.CreateFmt('ring buffer is full: %d of %d slots taken', [FCount, Capacity]);
  FItems[FRear] := Item;
  FRear := (FRear + 1) mod Capacity;
  Inc(FCount);
end;

function TRingBuffer<T>.Pop: T;
begin
  if FCount = 0 then
    raise EBufferEmpty.Create('ring buffer is empty');
  Result := FItems[FFront];
  FFront := (FFront + 1) mod Capacity;
  Dec(FCount);
end;

end.

generics/ring_delphi.pas

program RingDelphi;
{ The ring buffer of marks that the figure steps through: 4 slots, nine operations, one refused. }
{$mode delphi}

uses
  SysUtils, RingBuffer;

var
  ring: TRingBuffer<Integer>;

procedure Show(const What: string);
begin
  WriteLn(What, ': front ', ring.Front, ', rear ', ring.Rear, ', count ', ring.Count);
end;

procedure Push(Value: Integer);
begin
  try
    ring.Push(Value);
    Show(Format('Push(%d)', [Value]));
  except
    on E: EBufferFull do WriteLn(Format('Push(%d): %s: %s', [Value, E.ClassName, E.Message]));
  end;
end;

procedure Pop;
var
  value: Integer;
begin
  value := ring.Pop;
  Show(Format('Pop = %d', [value]));
end;

begin
  ring := TRingBuffer<Integer>.Create(4);
  try
    Push(72); Push(58); Push(91);
    Pop;
    Push(66); Push(40); Push(85);
    Pop; Pop;
  finally
    ring.Free;
  end;
end.

generics/ring_objfpc.pas

program RingObjFpc;
{ The same unit, used from an objfpc program: here every use of the generic is written with specialize. Each unit
  is compiled in its own mode. }
{$mode objfpc}{$H+}

uses
  SysUtils, RingBuffer;

type
  TNames = specialize TRingBuffer<string>;

var
  names: TNames;
begin
  names := TNames.Create(2);
  try
    names.Push('Asha');
    names.Push('Ravi');
    WriteLn('objfpc: popped ', names.Pop, ', then ', names.Pop);
    try
      names.Pop;
    except
      on E: EBufferEmpty do WriteLn('objfpc: ', E.ClassName, ': ', E.Message);
    end;
  finally
    names.Free;
  end;
end.
A list and a dictionary, then the ring buffer from delphi and objfpc code Python · generics_session.py
from session import run

# Generics.Collections in objfpc mode, then a generic ring buffer written in delphi mode, used from a delphi program
# and from an objfpc one. -vn- hides the notes that Generics.Collections' own code produces when it is compiled.
run("""
cd generics
fpc -l- -vi- -vn- collections.pas
./collections
fpc -l- -vi- ring_delphi.pas
./ring_delphi
fpc -l- -vi- ring_objfpc.pas
./ring_objfpc
""")

Output

$ cd generics
$ fpc -l- -vi- -vn- collections.pas
generics.dictionaries.inc(191,92) Warning: Constructing a class "TCustomDictionaryEnumerator$4$crc398437A0" with abstract method "DoMoveNext"
generics.dictionaries.inc(191,92) Warning: Constructing a class "TCustomDictionaryEnumerator$4$crc398437A0" with abstract method "GetCurrent"
generics.dictionaries.inc(191,92) Warning: Constructing a class "TCustomDictionaryEnumerator$4$crc74054DAE" with abstract method "DoMoveNext"
generics.dictionaries.inc(191,92) Warning: Constructing a class "TCustomDictionaryEnumerator$4$crc74054DAE" with abstract method "GetCurrent"
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./collections
sorted: 58 66 72 91
Asha: 91
Meera in the book: FALSE, entries: 2
$ fpc -l- -vi- ring_delphi.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./ring_delphi
Push(72): front 0, rear 1, count 1
Push(58): front 0, rear 2, count 2
Push(91): front 0, rear 3, count 3
Pop = 72: front 1, rear 3, count 2
Push(66): front 1, rear 0, count 3
Push(40): front 1, rear 1, count 4
Push(85): EBufferFull: ring buffer is full: 4 of 4 slots taken
Pop = 58: front 2, rear 1, count 3
Pop = 91: front 3, rear 1, count 2
$ fpc -l- -vi- ring_objfpc.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./ring_objfpc
objfpc: popped Asha, then Ravi
objfpc: EBufferEmpty: ring buffer is empty

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 generics_session.py

The four warnings of the first build come from generics.dictionaries.inc, the library’s own source, when TDictionary is specialized in Free Pascal 3.2.2; your code is not their cause, and the program ran correctly. -vn- hid the library’s notes. The ring buffer printed its front, rear and count after every operation. Step through the same four slots and nine operations, with the slot each item lands in:

Interactive · step through it The ring buffer of ring_delphi.pas: four slots, nine operations front and rear move round a fixed set of slots and wrap from the last slot to slot 0.
Circular queue of 4 slots: 0: empty, 1: empty, 2: empty, 3: empty. front 0, rear 0, 0 in use.0123frontrearwraps around
  • being moved or written

Pseudo-code

  1. enqueue(x): if count = capacity: error "full"
  2. a[rear] ← x; rear ← (rear + 1) mod capacity; count ← count + 1
  3. dequeue(): if count = 0: error "empty"
  4. x ← a[front]; front ← (front + 1) mod capacity; count ← count − 1; return x

A circular queue with 4 slots. front is where the next item leaves, rear where the next one goes; both wrap around to slot 0 after slot 3.

When it finishes: 2 items queued.

Items 0
Operations 0

Push(66) wrote slot 3 and wrapped rear round to 0; Push(40) filled the ring, so Push(85) raised EBufferFull and changed nothing; the two last pops took 58 and 91 in the order they arrived. No item ever moved, only front and rear. In objfpc mode the same unit served a ring of strings, specialize TRingBuffer<string>, and its empty Pop raised EBufferEmpty.

Checkpoint

Check: Generic containers in both modes

  1. Question 1 of 2 A ring of 4 slots has front 1, rear 3 and count 2. Push(66) puts 66 in slot 3. Where is rear afterwards?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Slot 0: (3 + 1) mod 4

    Push and Pop move rear and front with (index + 1) mod capacity, so no item ever has to move.

  2. Question 2 of 2 RingBuffer.pas declares TRingBuffer<T> in delphi mode. How does an objfpc program write a ring of strings?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: specialize TRingBuffer<string>: each unit keeps its own mode, and objfpc code writes specialize

    A mode belongs to a file. A project can mix units written for Delphi with objfpc units, and each uses generics in its own syntax.

Interfaces count their references and free the object themselves

An interface variable is different from a class variable. With the default COM-style interfaces, the Reference Guide says every assignment to an interface variable updates a reference count, every variable that goes out of scope decreases it, and when it reaches zero the object that implements the interface is usually freed. That is reference counting: Freeing an object automatically when the last reference to it goes: each new reference adds one to a count and each released one subtracts one. Free Pascal counts COM-style interface references and AnsiStrings this way., the same mechanism that frees AnsiStrings, and TInterfacedObject implements it for your classes:

grader.pas, an interface and a class that implements it PAS · interfaces/grader.pas
unit Grader;
{ An interface and a class that implements it. TInterfacedObject counts the interface references to the object and
  frees it when the count drops to zero. }
{$mode objfpc}{$H+}

interface

type
  IGrader = interface
    ['{6F1C2A4E-8B3D-4C55-9E21-7A0B3C4D5E6F}']
    function Grade(Marks: Integer): string;
  end;

  TPassGrader = class(TInterfacedObject, IGrader)
  public
    destructor Destroy; override;
    function Grade(Marks: Integer): string;
  end;

implementation

destructor TPassGrader.Destroy;
begin
  WriteLn('  TPassGrader destroyed');
  inherited Destroy;
end;

function TPassGrader.Grade(Marks: Integer): string;
begin
  if Marks >= 40 then
    Result := 'pass'
  else
    Result := 'fail';
end;

end.
A counted object, the same object freed by hand, and CORBA-style interfaces Python · interfaces_session.py
from session import run

# COM-style interfaces (the default) count references and free the object themselves; an object also freed by hand
# fails; CORBA-style interfaces count nothing, so the program frees the object itself.
run("""
cd interfaces
fpc -l- -vi- counted.pas
./counted
fpc -l- -vi- mixed.pas
!./mixed
fpc -l- -vi- corba.pas
./corba
""")

Output

$ cd interfaces
$ fpc -l- -vi- counted.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./counted
calling GradeOnce
  after Create: RefCount = 0
  after g := obj: RefCount = 1
  72 is a pass
  leaving GradeOnce
  TPassGrader destroyed
back in the program
$ fpc -l- -vi- mixed.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./mixed
  72 is a pass
  TPassGrader destroyed
An unhandled exception occurred at $…:
EInvalidPointer: Invalid pointer operation
  $…
  $…
  $…
  $…
[exit status 217]
$ fpc -l- -vi- corba.pas
-macosx_version_min has been renamed to -macos_version_min
ld: warning: -multiply_defined is obsolete
$ ./corba
corba: 35 is a fail
corba: g := nil destroyed nothing
  TPassGrader destroyed

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 interfaces_session.py

counted.pas never calls Free, yet the object was destroyed as GradeOnce returned. mixed.pas calls obj.Free while an interface variable still counts the object, and the program stopped with EInvalidPointer and exit status 217. The Reference Guide warns about exactly this: freeing such an instance early is invalid, because the compiler releases its interface references later and that release tries to free it again. Step through both runs:

Interactive · step by step Who frees an object that an interface reference counts Step through counted.pas, where the count frees the object, then mixed.pas, where Free is called as well.
Who frees an object that an interface reference counts, step 7 of 7. Showing obj: TPassGrader, g: IGrader, The TPassGrader object (destroyed, still counted). In focus: g: IGrader, The TPassGrader object.does not countcountsobj: TPassGraderplain referenceg: IGradercounted referenceThe TPassGrader objectdestroyed, still countedEInvalidPointer

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Create. obj := TPassGrader.Create makes the object. A plain object variable does not count references, so counted.pas printed RefCount = 0.
  2. An interface variable counts. g := obj stores a reference in an interface variable, and that adds one: RefCount = 1.
  3. Calls go through the interface. g.Grade(72) calls the object's method through the interface and returns pass. The count does not change.
  4. The count frees the object. When GradeOnce returns, g goes out of scope and its reference is released. The count falls to 0 and the object frees itself: TPassGrader destroyed printed before back in the program, with no Free anywhere.
  5. Now mixed.pas. mixed.pas sets up the same two references: obj, which does not count, and g, which does. The object is alive with RefCount 1.
  6. Free by hand. obj.Free destroys the object while g still counts it. The destructor printed TPassGrader destroyed, but obj.Free did not return normally.
  7. The program stops. The line after obj.Free never ran: the program stopped with EInvalidPointer: Invalid pointer operation and exit status 217. Choose one owner: the count, or Free, never both.

Choose one owner for each object. Reach it only through interfaces and let the count free it, or switch the counting off: the Programmer’s Guide says {$interfaces corba} makes interfaces that have no parent and are not reference counted, so the programmer does the bookkeeping. corba.pas set its interface variable to nil, which destroyed nothing, and freed the object itself in a finally block.

Key idea

Every object has exactly one owner, a variable with try … finally, an owning list, or an interface count, and only that owner frees it.

Checkpoint

Check: Reference-counted interfaces

  1. Question 1 of 2 In counted.pas, GradeOnce creates a TPassGrader, keeps it in an interface variable and never calls Free. When is the object destroyed?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: When GradeOnce returns, as the last interface reference to it goes

    RefCount was 0 after Create and 1 after g := obj. When g went out of scope the count returned to 0 and the object freed itself.

  2. Question 2 of 2 mixed.pas frees an object with obj.Free while an interface variable still counts it. What did the run do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The destructor ran, then the program stopped with EInvalidPointer and exit status 217

    Choose one owner: reach the object only through interfaces and let the count free it, or use CORBA-style interfaces and free it yourself.

Practise: port the ring buffer to Python

The exercise ports TRingBuffer<T> to Python with its contract intact: the same slots and indexes, the same two exception classes with the same messages, and read-only properties for count, capacity, front and rear, as the Pascal class declares them. Its sample tests replay ring_delphi.pas and ring_objfpc.pas line for line.

Exercise · Medium · Python

Port the generic ring buffer and its exceptions to Python

The lesson's TRingBuffer<T> keeps items of any type in a fixed number of slots, and two exception classes say what went wrong. Port it to Python in ring.py, keeping its contract exactly, so that Python code and Pascal code built on it can be tested against the same cases.

  • BufferFull and BufferEmpty are exception classes derived from Exception, as EBufferFull and EBufferEmpty derive from SysUtils' Exception.
  • RingBuffer(capacity) starts empty, with front and rear at slot 0.
  • push(item) puts the item in slot rear and moves rear one slot on, from the last slot back to 0. When every slot is taken it raises BufferFull with the message ring buffer is full: 4 of 4 slots taken (with the buffer's own numbers), and it changes nothing.
  • pop() returns the item in slot front and moves front on in the same way. When the buffer is empty it raises BufferEmpty("ring buffer is empty").
  • count, capacity, front and rear are read-only properties, as in the Pascal class: reading them works, assigning to them raises AttributeError.

The sample tests replay ring_delphi.pas from the lesson, nine operations on four slots with one push refused, and ring_objfpc.pas, two names and an empty pop.

Starter code · ring.py

class BufferFull(Exception):
    """Raised by push when every slot is taken."""


class BufferEmpty(Exception):
    """Raised by pop when there is nothing to take."""


class RingBuffer:
    """A ring buffer of fixed capacity, like the lesson's TRingBuffer<T>."""

    def __init__(self, capacity):
        # Replace this class body with your code.
        self.items = []
        self.capacity = capacity

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()
The sample tests · test_ring.py
from ring import BufferEmpty, BufferFull, RingBuffer


def state(ring):
    return (ring.front, ring.rear, ring.count)


def raised(action):
    """The exception that action() raises, or None."""
    try:
        action()
    except Exception as error:  # noqa: BLE001 - the tests look at the class and the message
        return error
    return None


def test_three_pushes_and_a_pop():
    """ring_delphi.pas: Push(72), Push(58), Push(91), then Pop = 72"""
    ring = RingBuffer(4)
    ring.push(72)
    assert state(ring) == (0, 1, 1)
    ring.push(58)
    ring.push(91)
    assert state(ring) == (0, 3, 3)
    assert ring.pop() == 72
    assert state(ring) == (1, 3, 2)


def test_rear_wraps_round():
    """Push(66) puts rear back at slot 0; Push(40) fills the ring"""
    ring = RingBuffer(4)
    for value in (72, 58, 91):
        ring.push(value)
    ring.pop()
    ring.push(66)
    assert state(ring) == (1, 0, 3)
    ring.push(40)
    assert state(ring) == (1, 1, 4)


def test_full_ring_refuses_and_keeps_its_state():
    """Push(85) on a full ring raises BufferFull with the Pascal message and changes nothing"""
    ring = RingBuffer(4)
    for value in (72, 58, 91):
        ring.push(value)
    ring.pop()
    ring.push(66)
    ring.push(40)
    error = raised(lambda: ring.push(85))
    assert isinstance(error, BufferFull)
    assert str(error) == "ring buffer is full: 4 of 4 slots taken"
    assert state(ring) == (1, 1, 4)


def test_pops_come_out_in_order():
    """after the refused push, Pop gives 58 then 91, as the recorded run printed"""
    ring = RingBuffer(4)
    for value in (72, 58, 91):
        ring.push(value)
    ring.pop()
    ring.push(66)
    ring.push(40)
    raised(lambda: ring.push(85))
    assert ring.pop() == 58
    assert ring.pop() == 91
    assert state(ring) == (3, 1, 2)


def test_any_item_type_and_empty_pop():
    """ring_objfpc.pas: two names, then a pop from the empty ring raises BufferEmpty"""
    names = RingBuffer(2)
    names.push("Asha")
    names.push("Ravi")
    assert (names.pop(), names.pop()) == ("Asha", "Ravi")
    error = raised(names.pop)
    assert isinstance(error, BufferEmpty)
    assert str(error) == "ring buffer is empty"
    assert issubclass(BufferEmpty, Exception) and issubclass(BufferFull, Exception)
    assert not issubclass(BufferEmpty, BufferFull) and not issubclass(BufferFull, BufferEmpty)


def test_read_only_properties():
    """count, capacity, front and rear can be read but not assigned"""
    ring = RingBuffer(3)
    assert ring.capacity == 3 and ring.count == 0
    for name in ("count", "capacity", "front", "rear"):
        assert isinstance(raised(lambda: setattr(ring, name, 1)), AttributeError)
A hint

Keep a list of capacity slots and three numbers, front, rear and count, exactly as the Pascal class does; (rear + 1) % capacity moves an index on and wraps it. Python's @property without a setter gives you a read-only property, like property Count: Integer read FCount.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then take the self-check on the lesson’s five ideas, from ownership to interfaces. It opens with the idea your answers so far leave weakest, and each pick is explained on the spot:

Practice that adapts to you

Practise this lesson

15 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 15 What does a variable declared as s: TStudent hold?

    Choose one answer.

    Show the answer: Question 1 of 15

    Answer: A pointer to an object on the heap, nil or garbage until Create assigns one

    Class variables hold references to heap objects, records hold values. The previous lesson's record copy changed nothing in the original; two class variables can share one object.

  2. Question 2 of 15 Why is s := TStudent.Create written before try, not inside it?

    Choose one answer.

    Show the answer: Question 2 of 15

    Answer: If Create fails, there is no object and s is unassigned, so the finally block must not free it

    Create, then try ... finally Free end: the try block is only entered once there is an object to free.

  3. Question 3 of 15 heaptrc reports one unfreed block of 24 bytes per student processed by a loop. Which change fixes it?

    Choose one answer.

    Show the answer: Question 3 of 15

    Answer: Free each student in a finally block inside the loop, or add them to a list that owns them

    A leak per record grows with the work. Give every object an owner: a local variable with try ... finally, or an owning container.

  4. Question 4 of 15 What does turning a public field into a property with a setter change for the code that uses it?

    Choose one answer.

    Show the answer: Question 4 of 15

    Answer: Nothing in how it is written; the assignments now go through the setter

    Properties let a class add checks to a field later without touching its callers.

  5. Question 5 of 15 In marked.pas, s.Marks := 191 raised EBadMarks. What was s.Marks afterwards?

    Choose one answer.

    Show the answer: Question 5 of 15

    Answer: 91, the value before the failed assignment

    Check, then store: a setter that raises before it writes leaves the object as it was.

  6. Question 6 of 15 Which handler can catch an EBadMarks, which derives from EMarksError, which derives from Exception?

    Choose one answer.

    Show the answer: Question 6 of 15

    Answer: on E: EBadMarks, on E: EMarksError and on E: Exception all match it

    Exception hierarchies let a caller choose how specific to be: one class, a family, or everything.

  7. Question 7 of 15 parse_marks.pas ends with an exception that no handler catches. What exit status did the session show?

    Type a number.

    Show the answer: Question 7 of 15

    Answer: 217 exit status

    The User's Guide lists run-time error 217 as an unhandled exception; SysUtils' default handler prints the class and message and ends the program with it.

  8. Question 8 of 15 StrToInt('ninety') raised an exception that parse_marks.pas did not define itself. Which class, and which handler caught it?

    Choose one answer.

    Show the answer: Question 8 of 15

    Answer: EConvertError, caught by on E: EConvertError do

    Library code raises its own classes. A handler for your base class does not catch them, which is why parse_marks.pas has a third handler.

  9. Question 9 of 15 A batch job must skip bad input lines, report them, and stop on any real bug. How should its handler around each line look?

    Choose one answer.

    Show the answer: Question 9 of 15

    Answer: Catch your own base class, such as EMarksError, and EConvertError; let everything else reach the top

    Handle what you expect and can describe; let the unexpected stop the program with exit status 217 and its class and message.

  10. Question 10 of 15 In objfpc mode, how do you declare a list of integers from Generics.Collections?

    Choose one answer.

    Show the answer: Question 10 of 15

    Answer: TMarkList = specialize TList<Integer>;

    A specialization fills in a generic's type parameters. objfpc says so with specialize; delphi mode writes the name alone.

  11. Question 11 of 15 The ring of 4 slots is full when Push(85) is called. What did ring_delphi.pas print, and what changed?

    Choose one answer.

    Show the answer: Question 11 of 15

    Answer: EBufferFull: ring buffer is full: 4 of 4 slots taken, and nothing changed

    A fixed ring has two edge cases, full and empty, and the class reports each with its own exception class.

  12. Question 12 of 15 Building collections.pas printed four warnings from generics.dictionaries.inc about a class with abstract methods. What do you do?

    Choose one answer.

    Show the answer: Question 12 of 15

    Answer: Note that they come from the library's own source when TDictionary is specialized, and check that the program runs correctly

    Read where a warning points before acting on it: here it pointed into Free Pascal 3.2.2's generics library, not into the lesson's code.

  13. Question 13 of 15 What kind of interface does Free Pascal use unless you say otherwise?

    Choose one answer.

    Show the answer: Question 13 of 15

    Answer: COM-style: descended from IUnknown and reference counted

    {$interfaces corba} switches to interfaces without counting, where the programmer frees the objects.

  14. Question 14 of 15 In corba.pas, g := nil was assigned to the interface variable. What happened to the object?

    Choose one answer.

    Show the answer: Question 14 of 15

    Answer: Nothing: CORBA-style interfaces count nothing, so the program freed it itself in finally

    With CORBA-style interfaces the interface is only a view of the object; ownership stays with the code that created it.

  15. Question 15 of 15 A TPassGrader is passed to a library as an IGrader and also kept in a list of graders for your own use. Which ownership plan is safe?

    Choose one answer.

    Show the answer: Question 15 of 15

    Answer: Keep only interface references, also in your list, and let the count free it

    One owner per object: with COM interfaces the count is the owner, so every reference you keep must be an interface reference.

Interview questions

Warm-up (fresher to mid-level): Why does Object Pascal code write Create, then try … finally Free end, and why does Create come before try?

A class instance lives on the heap and nothing frees it automatically, so whoever creates an object must free it on every way out of the code that uses it, including an exception. try … finally guarantees that: the finally block runs whether the try block ends normally, exits early or raises, and then lets any exception carry on to a handler. Create goes before try because the constructor may fail. If it raises, the Reference Guide says the destructor runs automatically, and the variable was never assigned; a Free in the finally block would then work on garbage. With Create first, try is only entered once there is an object to free. In the lesson, leak.pas without this pattern left one 24-byte block, and fixed.pas with it left none.

Two questions of the kind a team asks someone who will own Object Pascal code:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to senior

    A long-running Free Pascal service grows by about 24 bytes for every student record it processes, and after a busy week it is restarted to get its memory back. How do you find the leak, fix it, and keep it from coming back?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A procedure stops with EInvalidPointer: Invalid pointer operation, sometimes at a Free call and sometimes when it returns. The object involved implements an interface. What is the likely cause, and how do you fix the design rather than the symptom?

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Who frees an Object Pascal class instance?

    Your own code: a class variable points to an object on the heap, and nothing frees it until Free runs its destructor. leak.pas, which never did, ended with one 24-byte block unfreed.

  2. Why is s := TStudent.Create(…) written before try, not inside it?

    If the constructor fails, the destructor runs by itself and s is never assigned, so a Free in finally would act on garbage. Created first, the object exists whenever try is entered.

  3. What does the build option -gh add to a Free Pascal program?

    The heaptrc unit: it records every allocation and, at exit, reports the blocks that were never freed, such as 1 unfreed memory blocks : 24.

  4. What does a list created with TObjectList.Create(True) do with the objects put in it?

    It owns them: freeing the list frees each of them too, so the program frees only the list.

  5. Given property Marks: Integer read FMarks write SetMarks, what runs for s.Marks := 191?

    A call to SetMarks(191), which can refuse the value by raising. Reading s.Marks reads FMarks directly, and a property with no write cannot be assigned at all.

  6. In which order do except handlers for EBadMarks and its parent EMarksError go?

    The child first. Handlers are tried top to bottom and the first one whose class matches wins, so a parent placed first would catch its children as well.

  7. What happens when a Free Pascal exception reaches no handler at all?

    SysUtils' default handler reports that an unhandled exception occurred, with the class and the message, and ends the program with exit status 217.

  8. Why must you never call Free on an object still held by a COM-style interface variable?

    The interface references are counted, and releasing the last one frees the object a second time; in the lesson that stopped with EInvalidPointer. Give each object one owner: the count, or {$interfaces corba} and your own Free.

Key takeaways

  • A class instance lives on the heap until it is freed; Create, then try … finally Free end frees it on every path, and building with -gh makes heaptrc list any block that was never freed.
  • A list created with TObjectList.Create(True) owns its items and frees them with itself.
  • A property reads and writes like a field but goes through its accessors, so a setter can refuse a bad value and a property without write cannot be assigned at all.
  • Handlers are tried in order and the first whose class matches wins, so specific classes come before their parents; finally blocks run first, and an exception nobody handles ends the program with exit status 217.
  • Generics.Collections and your own generics work in both modes, with specialize in objfpc and without it in delphi mode, and each unit is compiled in the mode it declares.
  • COM interfaces count references and free their object when the count reaches zero, so never also call Free; {$interfaces corba} switches the counting off.

Terms in this lesson

heaptrc
A unit of Free Pascal's run-time library that counts every memory block a program allocates and frees, and prints a report when the program ends. Building with -gh adds it; a block still allocated at the end is a leak.
property
A class member that reads and writes like a field but goes through the accessors it names, as in property Marks: Integer read FMarks write SetMarks. A property without a write accessor is read-only, and the compiler refuses an assignment to it.
reference counting
Freeing an object automatically when the last reference to it goes: each new reference adds one to a count and each released one subtracts one. Free Pascal counts COM-style interface references and AnsiStrings this way.
specialization
A type made from a generic by filling in its type parameters, such as TList<Integer> from TList<T>. objfpc mode writes it with the keyword specialize; delphi mode writes the type name alone.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.