Recent

Author Topic: what about an indexed record = irecord type ?  (Read 2475 times)

JoLt

  • Newbie
  • Posts: 2
what about an indexed record = irecord type ?
« on: September 17, 2025, 03:56:18 pm »
Working with records for a first Pascal project I'm stumped by the realisation i cannot just write out all record fields at once.
 
Code: Pascal  [Select][+][-]
  1. ...
  2. type
  3.     myrecord = record
  4.         rec : String;
  5.         type : Integer;
  6.         note : String;
  7.     end;
  8. ...
  9.  
  10. function myfunction : myrecord
  11. ...
  12.  
  13. begin
  14.  
  15.     writeln(myfunction());
  16.  
  17. end.
  18.  
  19.  
Assuming this is because records are not indexed I came up with this request since I have no need for a variant record or an object or a class.

Please let me know if this makes sense or if I'm better off using a different type anyway.
The choice for a record type here was made because the fields need to be of different types.

Best

JL

LemonParty

  • Hero Member
  • *****
  • Posts: 529
Re: what about an indexed record = irecord type ?
« Reply #1 on: September 17, 2025, 04:42:40 pm »
Pascal can't automatically serialize records like JS. Instead you have to implement additional function like this:
Code: Pascal  [Select][+][-]
  1. uses
  2.   SysUtils;
  3.  
  4. function ToString(const R: myrecord): String;
  5. begin
  6.   Result:= R.rec + ' ' + IntToStr(R.fType) + ' ' + R.note;
  7. end;
  8.  
Then you do:
Code: Pascal  [Select][+][-]
  1. {...}
  2. function myfunction : myrecord;
  3. {...}
  4. begin
  5.   Writeln(ToString(myfunction));
  6. end.
  7.  
« Last Edit: September 17, 2025, 04:49:08 pm by LemonParty »
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

LemonParty

  • Hero Member
  • *****
  • Posts: 529
Re: what about an indexed record = irecord type ?
« Reply #2 on: September 17, 2025, 05:00:52 pm »
Also it is important to clean up your record if you have managed types as strings in it. It can be done this way:
Code: Pascal  [Select][+][-]
  1. procedure Free(var R: myrecord);
  2. begin
  3.   R.rec:= '';
  4.   R.note:= '';
  5. end;
  6.  
And then:
Code: Pascal  [Select][+][-]
  1. {...}
  2. function myfunction : myrecord;
  3. {...}
  4. var
  5.   R: myrecord;
  6. begin
  7.   R:= myfunction;
  8.   Writeln(ToString(R));
  9.   Free(R);
  10. end.
  11.  
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

bytebites

  • Hero Member
  • *****
  • Posts: 787
Re: what about an indexed record = irecord type ?
« Reply #3 on: September 17, 2025, 05:06:56 pm »
Also it is important to clean up your record if you have managed types as strings in it. It can be done this way:
Code: Pascal  [Select][+][-]
  1. procedure Free(var R: myrecord);
  2. begin
  3.   R.rec:= '';
  4.   R.note:= '';
  5. end;
  6.  
And then:
Code: Pascal  [Select][+][-]
  1. {...}
  2. function myfunction : myrecord;
  3. {...}
  4. var
  5.   R: myrecord;
  6. begin
  7.   R:= myfunction;
  8.   Writeln(ToString(R));
  9.   Free(R);
  10. end.
  11.  
Absolutely unnecessary to free long string manually.

LemonParty

  • Hero Member
  • *****
  • Posts: 529
Re: what about an indexed record = irecord type ?
« Reply #4 on: September 17, 2025, 07:16:41 pm »
Quote
Absolutely unnecessary to free long string manually.

Look how many memory consume this program:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2.  
  3. uses
  4.   SysUtils;
  5.  
  6. type
  7.   TRec = record
  8.     A, B: String;
  9.   end;
  10.  
  11. function FillString: TRec;
  12. begin
  13.   Result.A:= 'String ' + IntToStr(Random($FFFF));
  14.   Result.B:= 'String ' + IntToStr(Random($FFFF));
  15. end;
  16.  
  17. const
  18.   Size = 1024 * 1024 * 32;
  19. var
  20.   RecArr: array of TRec;
  21.   i: Integer;
  22. begin
  23.   SetLength(RecArr, Size);
  24.   for i:= 0 to Size do
  25.     RecArr[i]:= FillString;
  26.   SetLength(RecArr, 0);{release records without freeing}
  27.   Writeln('Done');
  28.   Readln;
  29. end.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

bytebites

  • Hero Member
  • *****
  • Posts: 787
Re: what about an indexed record = irecord type ?
« Reply #5 on: September 17, 2025, 07:40:45 pm »
No diffence with or without  SetLength(RecArr, 0);

See this

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2.  
  3. uses
  4.   SysUtils;
  5.  
  6. type
  7.   TRec = record
  8.     A, B: String;
  9.   end;
  10.  
  11. function FillString: TRec;
  12. begin
  13.   Result.A:= 'String ' + IntToStr(Random($FFFF));
  14.   Result.B:= 'String ' + IntToStr(Random($FFFF));
  15. end;
  16.  
  17. const
  18.   Size = 1024 ;
  19. var
  20.   RecArr: array of TRec;
  21.   i: Integer;
  22. begin
  23.   SetLength(RecArr, Size);
  24.   for i:= 0 to Size-1 do
  25.     RecArr[i]:= FillString;
  26.   //SetLength(RecArr, 0);{release records without freeing}
  27.   fillchar(RecArr[0],100*sizeof(trec),0);  // causes memory leak
  28.   Writeln('Done');
  29.   Readln;
  30. end.
« Last Edit: September 17, 2025, 09:49:43 pm by bytebites »

LemonParty

  • Hero Member
  • *****
  • Posts: 529
Re: what about an indexed record = irecord type ?
« Reply #6 on: September 17, 2025, 08:08:18 pm »
Then try this:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2.  
  3. uses
  4.   SysUtils;
  5.  
  6. type
  7.   TRec = record
  8.     A, B: String;
  9.   end;
  10.  
  11. function FillString: TRec;
  12. begin
  13.   Result.A:= 'String ' + IntToStr(Random($FFFF));
  14.   Result.B:= 'String ' + IntToStr(Random($FFFF));
  15. end;
  16.  
  17. procedure Free(var R: TRec);
  18. begin
  19.   R.A:= '';
  20.   R.B:= '';
  21. end;
  22.  
  23. const
  24.   Size = 1024 * 1024 * 32;
  25. var
  26.   RecArr: array of TRec;
  27.   i: Integer;
  28. begin
  29.   SetLength(RecArr, Size);
  30.   for i:= 0 to Size do
  31.     RecArr[i]:= FillString;
  32.   for i:= 0 to Size do
  33.     Free(RecArr[i]);
  34.   SetLength(RecArr, 0);{release record without freeing}
  35.   Writeln('Done');
  36.   Readln;
  37. end.
  38.  

You have to free long strings otherwise they will lie in memory because refcount not decrease. Exeption is when a string is not a part of record or object, in this case it will manage automatically.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

bytebites

  • Hero Member
  • *****
  • Posts: 787
Re: what about an indexed record = irecord type ?
« Reply #7 on: September 17, 2025, 09:52:19 pm »


You have to free long strings otherwise they will lie in memory because refcount not decrease. Exeption is when a string is not a part of record or object, in this case it will manage automatically.

 SetLength(RecArr, 0) frees long strings.


LemonParty

  • Hero Member
  • *****
  • Posts: 529
Re: what about an indexed record = irecord type ?
« Reply #8 on: September 17, 2025, 10:47:02 pm »
You're confusing long string and ShortString. It is ShortString can be freed by releasing memory, long string is a pointer and have to be handled differently.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

speter

  • Hero Member
  • *****
  • Posts: 532
Re: what about an indexed record = irecord type ?
« Reply #9 on: September 18, 2025, 01:25:11 am »
Working with records for a first Pascal project I'm stumped by the realisation i cannot just write out all record fields at once.
 
Code: Pascal  [Select][+][-]
  1. ...
  2. type
  3.     myrecord = record
  4.         rec : String;
  5.         type : Integer;
  6.         note : String;
  7.     end;
  8. ...
  9.  
  10. function myfunction : myrecord
  11. begin
  12.     writeln(myfunction());
  13. end.

Maybe consider something like:

Code: Pascal  [Select][+][-]
  1. {$modeswitch advancedrecords}
  2. type
  3.   Tmyrecord = record
  4.     rec : String;
  5.     kind : Integer;
  6.     note : String;
  7.     function init(arec : string; akind : integer; anote : string);
  8.     function tostring : string;
  9.   end;
  10. var
  11.   foo : Tmyrecord;
  12.  
  13. procedure Tmyrecord.init(arec : string; akind : integer; anote : string);
  14. begin
  15.   rec := arec;
  16.   kind := akind;
  17.   note := anote;
  18. end;
  19.  
  20. function Tmyrecord.tostring : string;
  21. begin
  22.   result := format('rec=%s; kind=%d; note="%s',[rec,kind,note]);
  23. end;
  24.  
  25. procedure myfunction (z : Tmyrecord);
  26. begin
  27.     writeln(z.tostring);
  28. end;
  29.  
  30. begin
  31.   foo.init('some text',999,'a note');
  32.   myfunction(foo);
  33. end.

and, there are no memory leaks ;)
I climbed mighty mountains, and saw that they were actually tiny foothills. :)

Marc

  • Administrator
  • Hero Member
  • *
  • Posts: 2706
Re: what about an indexed record = irecord type ?
« Reply #10 on: September 18, 2025, 08:35:34 am »
You're confusing long string and ShortString. It is ShortString can be freed by releasing memory, long string is a pointer and have to be handled differently.

When a record contains managed types, it's members are finalized. So no need to do it manually. This also counts for an array of such record.
The fact that a longstring is a pointer internally doesn't matter. The compiler knows this and adds finalization code when such record goes out of scope.
So setting  the length of such array to zero finalizes the members.
Things are different when you don't store the record itself, but pointers to it. Then it is an array of pointers and you are responsible for disposing them.
Also in one of the examples in this thread, zeroing the array with fillchar() won't do the compiler magic and you have to do it yourself.
//--
{$I stdsig.inc}
//-I still can't read someones mind
//-Bugs reported here will be forgotten. Use the bug tracker

LemonParty

  • Hero Member
  • *****
  • Posts: 529
Re: what about an indexed record = irecord type ?
« Reply #11 on: September 18, 2025, 04:00:43 pm »
Quote
When a record contains managed types, it's members are finalized. So no need to do it manually. This also counts for an array of such record.
Wow! Don't know that compiler is smart enough to determine such circumstances. Is this applicable to objects?
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

Warfley

  • Hero Member
  • *****
  • Posts: 2066
Re: what about an indexed record = irecord type ?
« Reply #12 on: September 18, 2025, 09:41:59 pm »
Generally speaking Pascal allows for managed types, those are types that execute special code on Initialization (when the data is created), Finalization (when it is destroyed), Copy (when it is assigned) and Referencing (when you use it as a function parameter).

A type is managed if either:
1. It's a long string or a dynamic array
2. It's a COM interface
3. It's a record having management operators
4. It's a composite type that contains other managed types (e.g. a record containing a string will perform the management operations for the string inside).

Strings, Dynamic Arrays and COM interfaces are reference counted, meaning on Initialization the reference count is 1, on copy or reference it's incremented and finalization it is decremented and once it hits 0, the data is freed.
For management operators you can simply implement your own logic:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2. {$ModeSwitch advancedrecords}
  3.  
  4. type
  5.   TMyRec = record
  6.     class operator Initialize(var rec: TMyRec);
  7.     class operator Finalize(var rec: TMyRec);
  8.     class operator Copy(constref source: TMyRec; var dest: TMyRec);
  9.     class operator AddRef(var rec: TMyRec);
  10.   end;
  11.  
  12. class operator TMyRec.Initialize(var rec: TMyRec);
  13. begin
  14.   WriteLn('Initializing TMyRec at address: ', IntPtr(@rec));
  15. end;
  16.  
  17. class operator TMyRec.Finalize(var rec: TMyRec);
  18. begin
  19.   WriteLn('Finalizing TMyRec at address: ', IntPtr(@rec));
  20. end;
  21.  
  22. class operator TMyRec.Copy(constref source: TMyRec; var dest: TMyRec);
  23. begin
  24.   WriteLn('Copying TMyRec from: ', IntPtr(@source), ' to: ', IntPtr(@dest));
  25. end;
  26.  
  27. class operator TMyRec.AddRef(var rec: TMyRec);
  28. begin
  29.   WriteLn('Adding a TMyRec reference at address: ', IntPtr(@rec));
  30. end;
  31.  
  32. procedure Foo(r: TMyRec);
  33. begin
  34.  
  35. end;
  36.  
  37. procedure Test;
  38. var
  39.   r1, r2: TMyRec;
  40. begin
  41.   r1 := r2;
  42.   Foo(r1);
  43. end;

Wow! Don't know that compiler is smart enough to determine such circumstances. Is this applicable to objects?
Yes and no, with COM interfaces you can use reference counting for classes:
Code: Pascal  [Select][+][-]
  1. uses
  2.   Classes;
  3.  
  4. type
  5.   IMyInterface = interface
  6.   ['{B7E947EF-88AA-4DAC-A04A-436AC1D9820D}']
  7.   procedure Foo;
  8.   end;
  9.  
  10.   TMyClass = class(TInterfacedObject, IMyInterface)
  11.   public
  12.     procedure Foo;
  13.   end;
  14.  
  15. procedure TMyClass.Foo;
  16. begin
  17.   WriteLn('Foo on object: ', IntPtr(self));
  18. end;
  19.  
  20. procedure Test;
  21. var
  22.   i1, i2: IMyInterface;
  23. begin
  24.   i1 := TMyClass.Create as IMyInterface;
  25.   i2 := i1;
  26.   i1.Foo;
  27.   i2.Foo;
  28. end; // i1 and i2 are finalized, reducing the refcount to 0 freeing them automatically

But unlike Strings and Dynamic arrays which have strictly different member types, general classes can form circular dependencies in which case reference counting fails:
Code: Pascal  [Select][+][-]
  1. uses
  2.   Classes;
  3.  
  4. type
  5.   IMyInterface = interface
  6.   ['{B7E947EF-88AA-4DAC-A04A-436AC1D9820D}']
  7.   procedure SetRef(ref: IMyInterface);
  8.   end;
  9.  
  10.   TMyClass = class(TInterfacedObject, IMyInterface)
  11.   private
  12.     FRef: IMyInterface;
  13.   public
  14.     procedure SetRef(ref: IMyInterface);
  15.   end;
  16.  
  17. procedure TMyClass.SetRef(ref: IMyInterface);
  18. begin
  19.   FRef:=Ref;
  20. end;
  21.  
  22. procedure Test;
  23. var
  24.   i1, i2: IMyInterface;
  25. begin
  26.   i1 := TMyClass.Create as IMyInterface;
  27.   i2 := TMyClass.Create as IMyInterface;
  28.   // Create a circular reference, i1 now references i2 which references i1
  29.   i1.SetRef(i2);
  30.   i2.SetRef(i1);
  31. end; // Because i1 has a reference to i2 and vice versa, the reference counter doesn't reach 0 so you get a memory leak
Other languages like Swift or C++ that also use reference counting for this reason introduce the concept of weak pointers, which are pointers that do not increment the reference count. In Pascal there is no such concept for COM interfaces, but you could use the Classinstance rather than the interface type for that purpose (at least in the example above):
Code: Pascal  [Select][+][-]
  1. uses
  2.   Classes;
  3.  
  4. type
  5.   IMyInterface = interface
  6.   ['{B7E947EF-88AA-4DAC-A04A-436AC1D9820D}']
  7.   procedure SetRef(ref: IMyInterface);
  8.   end;
  9.  
  10.   TMyClass = class(TInterfacedObject, IMyInterface)
  11.   private
  12.     FRef: TMyClass;
  13.   public
  14.     procedure SetRef(ref: IMyInterface);
  15.   end;
  16.  
  17. procedure TMyClass.SetRef(ref: IMyInterface);
  18. begin
  19.   FRef:=Ref as TMyClass; // TMyClass is a raw pointer so it does not create reference counts
  20. end;
  21.  
  22. procedure Test;
  23. var
  24.   i1, i2: IMyInterface;
  25. begin
  26.   i1 := TMyClass.Create as IMyInterface;
  27.   i2 := TMyClass.Create as IMyInterface;
  28.   i1.SetRef(i2);
  29.   i2.SetRef(i1);
  30. end; // Because the circular reference uses raw pointers, the reference counter drops to 0 and there is no memory leak

Reference counting only introduces quite a small overhead and is therefore quite fast, much faster than other Garbage Collection methods. But because of this circular references, it is also a lot more complex and require quite a deep understanding of how the reference counting works. This is why most managed languages like C#, Go or Javascript usually use Garbage Colleaction instead, despite the performance penalty.


If you want to learn a bit more about reference counting, you could try to implement it yourself using management operators, it's a good exercise
« Last Edit: September 18, 2025, 09:44:02 pm by Warfley »

LemonParty

  • Hero Member
  • *****
  • Posts: 529
Re: what about an indexed record = irecord type ?
« Reply #13 on: September 18, 2025, 10:21:32 pm »
Thank you for explanation Warfley.

I am interesting if in this situation strings will be free:
Code: Pascal  [Select][+][-]
  1. {$mode ObjFPC}{$H+}
  2. type
  3.   TMyObject = object
  4.     A, B: String;
  5.   end;
  6.  
  7. var
  8.   ObjArr: array of TMyObject;
  9. begin
  10.   SetLength(ObjArr, 1024);
  11.   {setting A and B fields}
  12.   SetLength(ObjArr, 0);{Will it free A and B fields?}
  13. end.
  14.  

And what will be in this situation:
Code: Pascal  [Select][+][-]
  1. uses
  2.   SysUtils;
  3.  
  4. {$mode ObjFPC}{$H+}
  5. type
  6.   TMyObject = object
  7.   public
  8.     A, B: String;
  9.   end;
  10.  
  11. procedure UseObject;
  12. var
  13.   O: TMyObject;
  14. begin
  15.   O.A:= 'String ' + IntToStr(Random($FFFF));
  16.   O.B:= 'String ' + IntToStr(Random($FFFF));
  17. end; {Will O.A and O.B be freed after leaving this procedure?}
  18.  
  19. begin
  20.   UseObject;
  21. end.
  22.  
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

speter

  • Hero Member
  • *****
  • Posts: 532
Re: what about an indexed record = irecord type ?
« Reply #14 on: September 19, 2025, 01:45:10 am »
... I am interesting if in this situation strings will be free:
Code: Pascal  [Select][+][-]
  1. ...
  2. var
  3.   ObjArr: array of TMyObject;
  4. begin
  5.   SetLength(ObjArr, 1024);
  6.   {setting A and B fields}
  7.   SetLength(ObjArr, 0);{Will it free A and B fields?}
  8. end.
Code: Pascal  [Select][+][-]
  1. ...
  2. procedure UseObject;
  3. var
  4.   O: TMyObject;
  5. begin
  6.   O.A:= 'String ' + IntToStr(Random($FFFF));
  7.   O.B:= 'String ' + IntToStr(Random($FFFF));
  8. end; {Will O.A and O.B be freed after leaving this procedure?}
  9.  
  10. begin
  11.   UseObject;
  12. end.
Both work fine; Heaptrc reports no memory leaks.
See attached project. :)
I climbed mighty mountains, and saw that they were actually tiny foothills. :)

 

TinyPortal © 2005-2018