I remember vaguely from a while ago that some of the generic lists or datastructures has a limitation when it comes to storing records (that limitation doesn't affect objects).
Generics is handy, but some strange things made me switch back to classic datastructures if possible. For example, I saw "internal error" on compilation. Compiling the same code with another version of laz/fpc worked. And after that the first laz/fpc version worked again, a few times. There was also failure to compile due to a syntax error in interface section, when no such syntax error existed. All problems was resolved by removing all generics. Or compiling a second time. Except for the internal error. If you see these errors, be adviced where they come from.
Maybe these things can be worked around if you know how.
ChatGPT gave similar advice as Martin_fr:I asked this: Does TDictionary in freepascal work well with both objects and records?
ChatGPT answer:
Great question — TDictionary in Free Pascal (FPC) can work with both objects and records, but there are some caveats and best practices you should be aware of, especially when using records as either keys or values.
Let’s go over the details:
TDictionary<TKey, TValue> Overview (FPC mode Delphi-compatible)
In FPC 3.2+ (and especially 3.2.2+), TDictionary (from fgl or Generics.Collections) supports:
TKey and TValue can be primitive types, objects, records, strings, etc.
You can use custom comparers for key types that require specific equality/hash logic (very relevant for records!).
Using Records as Values (TValue)
This is safe and works well, e.g.:
type
TMyData = record
A: Integer;
B: String;
end;
var
Dict: TDictionary<Integer, TMyData>;
Rec: TMyData;
begin
Dict := TDictionary<Integer, TMyData>.Create;
Rec.A := 42;
Rec.B := 'Hello';
Dict.Add(1, Rec);
🔹 Internally, the record is copied by value when stored — so no problem there.
🔹 No need to manage memory unless the record holds managed types (e.g. dynamic arrays, strings, interfaces).
Using Records as Keys (TKey)
This works but you must be careful, because TDictionary uses:
GetHashCode → for hashing
Equals → for comparing keys
For simple records (no pointers, short strings, etc.), the default comparer does a bitwise compare, which may or may not match your logic.
Example:
type
TCoord = record
X, Y: Integer;
end;
var
Dict: TDictionary<TCoord, String>;
P: TCoord;
begin
Dict := TDictionary<TCoord, String>.Create;
P.X := 10;
P.Y := 20;
Dict.Add(P, 'Point A');
This works as long as the record is bitwise comparable.
Best Practice: Use a Custom Comparer for Records as Keys
If your record is more complex (e.g., floating-point fields, strings, or requires tolerance for equality), define a custom IEqualityComparer<T>, like this:
type
TCoordComparer = class(TInterfacedObject, IEqualityComparer<TCoord>)
function Equals(const Left, Right: TCoord): Boolean;
function GetHashCode(const Value: TCoord): Integer;
end;
function TCoordComparer.Equals(const Left, Right: TCoord): Boolean;
begin
Result := (Left.X = Right.X) and (Left.Y = Right.Y);
end;
function TCoordComparer.GetHashCode(const Value: TCoord): Integer;
begin
Result := Value.X * 31 + Value.Y;
end;
Then create the dictionary like this:
Dict := TDictionary<TCoord, String>.Create(TCoordComparer.Create);
Now you're safe and clear on how comparisons and hashes are handled.