Recent

Author Topic: FPC Unleashed (inline vars, statement expr, tuples, match, indexed/lazy labels)  (Read 35582 times)

creaothceann

  • Sr. Member
  • ****
  • Posts: 361
Compound assignment on properties

Available in {$mode unleashed}, no dedicated modeswitch.

Compound operators +=, -=, *=, /=, div=, mod=, and=, or=, xor=, shl=, shr= work directly on a class or record property that has both read and write accessors.

Stock FPC rejects `prop += x` with `Error: Variable identifier expected`, on the grounds that the read accessor and the write accessor can target different storage. So you have to spell it out by hand: [...].
The expansion is the same node tree the user would build manually: one getter call on the read side, the binary operator, one setter call on the write side. Side effects in the accessors fire exactly as in the manual rewrite, no more and no fewer.

The C-style operators (+=, -=, *=, /=) still need {$coperators on} or -Sc as in any FPC mode. The word-based operators (and=, or=, xor=, mod=, div=, shl=, shr=) work without it.

Could this also be done for Inc and Dec? This would then work even if C operators are off.

Strings could theoretically work too, but that's a bigger change from regular Pascal:

Code: Pascal  [Select][+][-]
  1. s *= i  // same effect as: for i := 2 to i do  s := s + original_s
  2.  
  3. Inc(var s : string;  const t : string = ' '                  )  //                     s := s + t
  4. Inc(var s : string;  const t : string = ' ';  i : integer = 1)  // for i := 1 to i do  s := s + t
  5. Inc(var s : string;                           i : integer    )  // for i := 1 to i do  s := s + ' '
  6.  
  7. Dec(var s : string;  const i : integer = 1                                      )  // SetLength(s, Length(s) - i)
  8. Dec(var s : string;  const t : string;  const f : TReplaceFlags = [rfReplaceAll])  // s := StringReplace(s, t, '', f)

Fibonacci

  • Hero Member
  • *****
  • Posts: 949
  • Behold, I bring salvation - FPC Unleashed
I can agree to Inc() / Dec() for numeric properties - same lowering as += / -=, just sidesteps {$coperators}. That's a clean extension worth doing.

Skipping the string overloads though - Inc / Dec mean increment/decrement, not append / repeat / StringReplace. I'd rather not multiplex one name across that many different operations.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Fibonacci

  • Hero Member
  • *****
  • Posts: 949
  • Behold, I bring salvation - FPC Unleashed
inc / dec on properties

Demo:

Code: Pascal  [Select][+][-]
  1. program prop_inc_dec;
  2.  
  3. {$mode unleashed}
  4.  
  5. type
  6.   TCounter = class
  7.   private
  8.     FVal: integer;
  9.     FByte: byte;
  10.     FName: string;
  11.     function GetVal: integer;
  12.     procedure SetVal(v: integer);
  13.     function GetByte: byte;
  14.     procedure SetByte(v: byte);
  15.     function GetName: string;
  16.     procedure SetName(const v: string);
  17.   public
  18.     property Val: integer read GetVal write SetVal;
  19.     property B: byte read GetByte write SetByte;
  20.     property Name: string read GetName write SetName;
  21.   end;
  22.  
  23. function TCounter.GetVal: integer;           begin Write('[get] '); Result := FVal; end;
  24. procedure TCounter.SetVal(v: integer);       begin Write('[set ', v, '] '); FVal := v; end;
  25. function TCounter.GetByte: byte;             begin Result := FByte; end;
  26. procedure TCounter.SetByte(v: byte);         begin FByte := v; end;
  27. function TCounter.GetName: string;           begin Result := FName; end;
  28. procedure TCounter.SetName(const v: string); begin FName := v; end;
  29.  
  30. var
  31.   c: TCounter;
  32. begin
  33.   c := TCounter.Create;
  34.   c.Val := 100;             WriteLn;
  35.   WriteLn('-- inc/dec on procsym integer property --');
  36.   inc(c.Val);               WriteLn(' Val=', c.Val);
  37.   inc(c.Val, 10);           WriteLn(' Val=', c.Val);
  38.   dec(c.Val);               WriteLn(' Val=', c.Val);
  39.   dec(c.Val, 5);            WriteLn(' Val=', c.Val);
  40.  
  41.   WriteLn('-- byte property --');
  42.   c.B := 250;
  43.   inc(c.B, 3);              WriteLn(' B=', c.B);
  44.   dec(c.B, 100);            WriteLn(' B=', c.B);
  45.  
  46.   WriteLn('-- string property --');
  47.   c.Name := 'foo';
  48.   // string property: inc/dec require ordinal type, must fail to compile.
  49.   // uncomment one of these lines to see the error:
  50.   //inc(c.Name);
  51.   //inc(c.Name, 'bar');
  52.   //dec(c.Name);
  53.   //dec(c.Name, 4);
  54.  
  55.   // for string concatenation use compound assignment instead:
  56.   c.Name += 'bar';
  57.  
  58.   writeln(c.Name);
  59.  
  60.   c.Free;
  61.   ReadLn;
  62. end.

Code: Text  [Select][+][-]
  1. [set 100]
  2. -- inc/dec on procsym integer property --
  3. [get] [set 101]  Val=[get] 101
  4. [get] [set 111]  Val=[get] 111
  5. [get] [set 110]  Val=[get] 110
  6. [get] [set 105]  Val=[get] 105
  7. -- byte property --
  8.  B=253
  9.  B=153
  10. -- string property --
  11. foobar



Coming up: FAM and/or String Interpolation tomorrow :)

Also got a super cool idea for records - I'm calling it Composable Records. Similar name to @Warfley's Record Composition, but a different (and IMO better) approach. More on that soon.
« Last Edit: May 05, 2026, 04:42:37 pm by Fibonacci »
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Fibonacci

  • Hero Member
  • *****
  • Posts: 949
  • Behold, I bring salvation - FPC Unleashed
New feature: Flexible Array Members

Modeswitch flexiblearrays, on by default in {$mode unleashed}.

C99-style flexible array members - declare a record with a variable-length tail using empty brackets as the last field. The record header has a fixed size, the tail extends as far as the allocation says it does, and sizeof(rec) reports only the fixed part.

The basics

Code: Pascal  [Select][+][-]
  1. type
  2.   PMessage = ^TMessage;
  3.   TMessage = packed record
  4.     code:   integer;
  5.     length: integer;
  6.     data:   array[] of byte;   // flexible array member
  7.   end;

The record and its tail live in one block. You allocate the fixed part plus payload in a single GetMem, write the trailing data through the FAM field by index, and free the whole block in one FreeMem:

Code: Pascal  [Select][+][-]
  1. var
  2.   msg: PMessage;
  3.   i:   integer;
  4. begin
  5.   GetMem(msg, sizeof(TMessage) + 1024);
  6.   msg^.code   := 42;
  7.   msg^.length := 1024;
  8.   for i := 0 to 1023 do
  9.     msg^.data[i] := byte(i);
  10.   // ... use msg ...
  11.   FreeMem(msg);
  12. end;

sizeof(TMessage) returns 8 here (just code and length). The FAM contributes nothing to the static size, so the math at the call site is the obvious sizeof(rec) + payload, with no off-by-one for a phantom one-element tail.

Memory layout

Code: Text  [Select][+][-]
  1.             +----------+----------+----------+ ... +----------+
  2. GetMem ---> | code (4) | length(4)| data[0]  |     | data[N]  |
  3.             +----------+----------+----------+ ... +----------+
  4.             ^                     ^
  5.             msg                   msg^.data
  6.             |<-- sizeof(rec) -->|<-- payload bytes -->|

The FAM starts at the offset that natural alignment gives the element type after the last fixed field. Padding is inserted automatically, exactly like for any other field.

Why a FAM and not the alternatives

Three patterns are commonly used today; each loses something the FAM keeps:
  • data: array[0..0] of byte (the C "struct hack") - {$rangechecks on} rejects every access past index 0, sizeof is one element too large, padding is implicit.
  • data: PByte to a separate buffer - two allocations, two frees, an extra indirection on every access, breaks the single-block layout used by Win32 structures.
  • case integer of 0: (data: array[0..high(integer)-X] of byte) - sizeof rolls over, high() lies, the compiler has no idea what the actual extent is.
The FAM gives you the inline layout, honest sizeof, working range-check setting, and a single allocation in one feature.

Restrictions

Enforced at parse time with dedicated error messages (parser_e_fam_*):
  • The FAM must be the last field of the record. No fields can follow.
  • The record must have at least one preceding field.
  • Only one FAM per record, and only one identifier per FAM declaration (a, b: array[] of byte is rejected).
  • A FAM is allowed only as a plain instance field of a plain record - not in class, object, class var, threadvar, or inside a variant (case) part.
  • A record containing a FAM cannot be embedded in another structured type.
  • A record containing a FAM cannot be the element type of an array.
  • A FAM-record cannot be a stand-alone variable, value parameter, or function result. Allocate via GetMem and use a pointer (PFamRec).
Reference parameters (var, const, constref, out) of FAM-record type stay legal; pointer-to-FAM-record (PFamRec) is unrestricted.

Comparison with array of T

A FAM is not a dynamic array. They share no run-time machinery:
  • Storage: array of T - heap block referenced by a managed pointer; FAM - inline tail of the containing record.
  • Lifetime: array of T - reference-counted, freed automatically; FAM - manual, freed with the containing block.
  • SetLength: array of T - resizes; FAM - not applicable, size fixed by the original GetMem.
  • Length(): array of T - current count; FAM - returns 0 (static length), runtime length is whatever you allocated.
  • Range checking: array of T - runtime check; FAM - none.
  • Overhead per record: array of T - one pointer + refcount; FAM - zero.
Want a managed, resizable array that lives elsewhere? Use array of T. Want a fixed-shape tail that sits inline behind the record header in one block? Use a FAM.

Debugger view

A FAM has no statically known length, so without help the debugger sees an empty array. The compiler emits a DWARF expression for the upper bound that reads the count at runtime from a sibling ordinal field of the same record. fpdebug and gdb evaluate that expression on every variable refresh and pretty-print the FAM with the right element count.

By default, the compiler picks the last ordinal field declared before the FAM as the count source. For the typical "header + count + payload" layout this needs no annotation:

Code: Pascal  [Select][+][-]
  1. type
  2.   PTokenPrivileges = ^TTokenPrivileges;
  3.   TTokenPrivileges = packed record
  4.     PrivilegeCount: DWORD;
  5.     Privileges:     array[] of LUID_AND_ATTRIBUTES;
  6.   end;

In the Local Variables / Watches panel, tp^.Privileges shows up with PrivilegeCount elements expanded.

If the count is not the last ordinal field before the FAM, bind it explicitly with the optional count clause:

Code: Pascal  [Select][+][-]
  1. type
  2.   TBatch = packed record
  3.     Count:    DWORD;
  4.     Reserved: DWORD;
  5.     Items:    array[] of TItem count Count;
  6.   end;

This is purely a debug-info detail - no runtime cost, no change to the record layout, no effect on sizeof or indexing.

Use cases

The pattern shows up wherever a fixed header is followed by a variable-length tail in one block of memory:
  • Win32 structures. BITMAPINFO, LOGPALETTE, TOKEN_GROUPS, TOKEN_PRIVILEGES, SOCKET_ADDRESS_LIST, SP_DRVINFO_DETAIL_DATA, and many more declare a trailing array as array[0..0] or ANYSIZE_ARRAY today, with all the problems above.
  • Network protocol frames. TCP/UDP packets, WebSocket frames, MQTT messages, custom IPC payloads.
  • File formats. BMP, WAV chunks, custom containers with a header and inline body.
  • Inline buffers in records. Storing a payload at the tail of a node avoids a second allocation and improves cache locality.
Demo

A real-world example using FAM with the Windows TOKEN_PRIVILEGES structure to enumerate the privileges of the current process token:

Code: Pascal  [Select][+][-]
  1. program tokenprivilegesdemo;
  2.  
  3. {$mode unleashed}
  4.  
  5. uses SysUtils, Windows;
  6.  
  7. const
  8.   SE_PRIVILEGE_REMOVED = $00000004; // missing in Windows unit
  9.  
  10. type
  11.   // FAM record - PrivilegeCount auto-binds as the count for Privileges
  12.   TOKEN_PRIVILEGES = packed record
  13.     PrivilegeCount: DWORD;
  14.     Privileges: array[] of LUID_AND_ATTRIBUTES;
  15.   end;
  16.  
  17. procedure fatal(msg: string);
  18. begin
  19.   writeln('FATAL: ', msg);
  20.   readln;
  21.   halt(1);
  22. end;
  23.  
  24. function describePrivilege(la: LUID_AND_ATTRIBUTES): (name: WideString; attrs: string);
  25. begin
  26.   var buf: array[0..255] of WideChar;
  27.   var len: dword := length(buf);
  28.   if not LookupPrivilegeNameW(nil, la.Luid, @buf[0], len) then buf[0] := #0;
  29.   result.name := WideCharToString(buf);
  30.  
  31.   result.attrs := '';
  32.   match all
  33.     la.Attributes and SE_PRIVILEGE_ENABLED            <> 0: result.attrs += 'ENABLED ';
  34.     la.Attributes and SE_PRIVILEGE_ENABLED_BY_DEFAULT <> 0: result.attrs += 'DEFAULT ';
  35.     la.Attributes and SE_PRIVILEGE_REMOVED            <> 0: result.attrs += 'REMOVED ';
  36.     la.Attributes and SE_PRIVILEGE_USED_FOR_ACCESS    <> 0: result.attrs += 'USED ';
  37.   end;
  38.   if result.attrs = '' then result.attrs := '-';
  39. end;
  40.  
  41. function queryTokenSize(token: THANDLE): (ok: boolean; size: dword);
  42. begin
  43.   result.size := 0;
  44.   GetTokenInformation(token, TokenPrivileges, nil, 0, result.size);
  45.   result.ok := GetLastError = ERROR_INSUFFICIENT_BUFFER;
  46. end;
  47.  
  48. procedure main;
  49. begin
  50.   // open access token of current process
  51.   var token: THANDLE;
  52.   if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, token) then fatal('OpenProcessToken failed');
  53.   defer CloseHandle(token);
  54.  
  55.   // first call, get required buffer size
  56.   var (ok, size) := queryTokenSize(token);
  57.   if not ok then fatal('GetTokenInformation (size query) failed');
  58.  
  59.   // second call, fetch actual data
  60.   var privs: ^TOKEN_PRIVILEGES;
  61.   GetMem(privs, size);
  62.   defer FreeMem(privs);
  63.  
  64.   if not GetTokenInformation(token, TokenPrivileges, privs, size, size) then fatal('GetTokenInformation failed');
  65.  
  66.   writeln('privilege count: ', privs^.PrivilegeCount);
  67.  
  68.   for var i := 0 to privs^.PrivilegeCount - 1 do begin
  69.     var (name, attrs) := describePrivilege(privs^.Privileges[i]);
  70.     writeln(Format('  %-42s  %s', [WideCharToString(@name[1]), attrs]));
  71.   end;
  72. end;
  73.  
  74. begin
  75.   main;
  76.   readln;
  77. end.

Output as a regular user:

Code: Text  [Select][+][-]
  1. privilege count: 6
  2.   SeLockMemoryPrivilege                       -
  3.   SeShutdownPrivilege                         -
  4.   SeChangeNotifyPrivilege                     ENABLED DEFAULT
  5.   SeUndockPrivilege                           -
  6.   SeIncreaseWorkingSetPrivilege               -
  7.   SeTimeZonePrivilege                         -



Lazarus IDE: rebranding

The Lazarus IDE fork that ships with Unleashed got a few cosmetic changes too: a new splash screen and "Unleashed" labels in a few places (about box, title bar, etc.) so it's clear at a glance which build you're running. Functionally identical, just visually distinct from stock Lazarus. Screenshots attached.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6491
THAT is good stuff :)

I'm very pleased you checked out the C99 standard for the good and complete definition it provides.  Good move on your part.

Now I have another feature request that is directly related to FAM and all other fields in a record. 

Feature request:

Add the possibility to specify alignment for the record as a whole and for individual record fields.  The more intuitive use is to have something along the lines of:
Code: Pascal  [Select][+][-]
  1. type
  2.   TMYRECORD = record align <1 or some power of 2>
  3.     AField       : byte;
  4.     AnotherField : byte  align 128;
  5.     MoreField    : int64 align 256;
  6.     Field2       : qword align 1;    { it would be nice if something like this generated a warning }
  7.   end;
  8.  
You may say that FPC already supports alignment using compiler directives.  The fact is that, supposedly it does but, if there is any way of making those things work, I am yet to find it and I've asked multiple times about how to make them work and, to this day I have never obtained a solution that worked for any alignment greater than 8.  That is supposed to work at the record declaration level, as of now, FPC does not provide any facilities to control individual record field alignment.   
Having alignment at the record and field level work as its supposed to would really ease porting some C structures to FPC (and allow dealing with hardware alignment requirements with ease, it's supposed to be easy.)
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Fibonacci

  • Hero Member
  • *****
  • Posts: 949
  • Behold, I bring salvation - FPC Unleashed
THAT is good stuff :)

Glad you find it useful :) Hope others do too. Bit surprising FPC didn't have it already, given how mature the project is. Delphi probably doesn't either, but don't quote me - I don't use it.

This is why I like FPC - the gaps and the open source. If everything were already there I'd have nothing to play with. C is too "done" for that kind of fun. So far I'm really enjoying the ride ;)



Add the possibility to specify alignment for the record as a whole and for individual record fields.

Adding to the list, it'll land at some point. The exact form is still TBD though: I have a small overhaul of records in the pipeline (the "Composable Records" thing I teased earlier), and I'd rather plan the alignment story together with that than bolt it on now and rewrite later.

The use cases are real:
  • SIMD-friendly layouts (SSE / AVX / AVX-512)
  • cacheline-aware structs - lock-free atomics, false-sharing avoidance
  • hardware / OS API structures
  • C/C++ interop
Stock FPC's {$ALIGN} / {$PACKRECORDS} really don't work above 8, so this fills a real gap. On the roadmap :)



EDIT: Big thanks to @440bx and @creaothceann for the steady stream of feature ideas - and to everyone else chiming in. Keep them coming.
« Last Edit: May 07, 2026, 05:43:53 am by Fibonacci »
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Okoba

  • Hero Member
  • *****
  • Posts: 660
@Fibonacci
1- Thank you very much for the {$modeswitch implicitgenerics}!
2- I like the 'is not' and 'not in' features. They seem small but lovely. Thank you.
3- Can yout support managed records Finalize with autofree?
4- I also request for the way @440bx suggested the we can attach a defer to a variable, so in case of custom object, it calls its own function on exit. You said you check for values initializations by boolean flags and using one try-finally block. So maybe we can use that way to know if the value is initializes and only then call for the defer function (check in the try-finally block).
5- I noticed that for console programs, they do not follow the ProjectN naming and they write app as the name. It looks off compare to other kind of projects. I also suggest rename of newly added main procedure to Main. As almost any standard function names in Pascal are in Pascal case and this one looks off or unoffisal.

6- A sample about this topic (https://forum.lazarus.freepascal.org/index.php/topic,73286.msg574549.html#msg574549) I asked before:
Code: Pascal  [Select][+][-]
  1. program app;
  2.  
  3.   {$mode unleashed}
  4.  
  5. type
  6.   TTest1 = record
  7.     X: Integer;
  8.   end;
  9.  
  10.   TTest2 = record
  11.     X: Integer;
  12.     class operator Initialize(var AValue: TTest2);
  13.   end;
  14.  
  15.   class operator TTest2.Initialize(var AValue: TTest2);
  16.   begin
  17.     AValue.X := 1;
  18.   end;
  19.  
  20.   procedure Main;
  21.   var
  22.     T1: TTest1;
  23.     T2: TTest2;
  24.   begin
  25.     WriteLn(T1.X);
  26.     WriteLn(T2.X); //project1.lpr(26,13) Warning: Local variable "T2" of a managed type does not seem to be initialized
  27.   end;
  28.  
  29. begin
  30.   Main;
  31. end.                                      
                 
The T2 warn seems wrong.

7- About with blocks with var. Is it possible to use with records? how?
Code: Pascal  [Select][+][-]
  1.     with var T:TTest1 do
  2.       X:=10;          
  3.  

8- About loop counters, what do you think about this:
Code: Pascal  [Select][+][-]
  1.   function Counter: Integer;
  2.   begin
  3.     Result := -1;
  4.   end;
  5.  
  6.   procedure Main;
  7.   var
  8.     I: Integer;
  9.   begin
  10.     I := 0;
  11.     for I := 1 to Counter do
  12.       if I = 3 then
  13.         Break;
  14.     WriteLn(I);
  15.   end;
Should it return the I as 1? As it started the loop, and then immediately exited.

9- What do you think about supporting SIMD functions and operators? That would be a great addition to write much faster codes.

schuler

  • Sr. Member
  • ****
  • Posts: 339
Just gave a star on both projects.

Fibonacci

  • Hero Member
  • *****
  • Posts: 949
  • Behold, I bring salvation - FPC Unleashed
@Okoba:

1, 2. Sure :) Glad you find them useful.

3. Could you clarify what you mean? autofree handles class instances; managed records have their own Initialize / Finalize operators that fire automatically on scope entry / exit.

4. This feels redundant and tricky to implement cleanly. What if the cleanup takes more than one argument? Or someone writes their own cleanup for something else? The syntax doesn't generalize.

Technically doable - the boolean flags are already there (they track whether the defer was reached, not strictly the variable initialization), so the runtime could call e.g. CloseHandle() automatically. But CloseHandle firing while the variable wasn't even assigned looks weird without context. You could even go further with inline-var style var autofree(CloseHandle) token: THANDLE - even more cryptic ;) Not high on the list.

5. Yes, the default project templates were changed in the Unleashed IDE - "Simple Program" now wraps your code in a main procedure instead of going directly into begin...end., so local variables are visible in the debugger. Why lowercase? Personal preference, don't fight it :P I'll probably add IDE options for both this and the program app -> project1 rename, but that's waiting for a settings tab.

6. Looks like a stock FPC false positive - the existing "managed type not initialized" warning doesn't recognize that class operator Initialize IS the initialization. Probably more of a job for the FPC core devs.

7. Currently not possible. Checked Delphi - same story. On my TODO list to look into.

8. No - I holds the last value the loop body actually saw. The loop didn't run at all (range is empty: 1 to -1), body saw nothing, I stays at 0 - the value you set before the loop. So... either the docs need a fix ("Empty range (for i := 10 to 1 do  ;) -> 10 (the from value, body never runs)") or the implementation does. Old engineering rule: docs lie sometimes, code is truth - and here I'd back the impl. Docs are the ones telling little lies ;)

9. Interested in the concept, but realistically out of scope for a one-person fork. SIMD as a first-class language feature is a massive undertaking - new type system entries, ABI work, optimizer integration, per-platform intrinsics. Question for FPC core devs.



Just gave a star on both projects.

Thank you.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6491
4. This feels redundant and tricky to implement cleanly. What if the cleanup takes more than one argument? Or someone writes their own cleanup for something else? The syntax doesn't generalize.

Technically doable - the boolean flags are already there (they track whether the defer was reached, not strictly the variable initialization), so the runtime could call e.g. CloseHandle() automatically. But CloseHandle firing while the variable wasn't even assigned looks weird without context. You could even go further with inline-var style var autofree(CloseHandle) token: THANDLE - even more cryptic ;) Not high on the list.
Just to elaborate on how the feature is supposed to be implemented.

1. it only applies when the resource release mechanism is limited to 1 parameter, which is the declared variable or, more parameters but only if the first one is the declared variable and the additional ones are constant values that can be determined based on the variable type.  In the FPC grammar, since the compiler has no idea how to determine what the additional parameters are supposed to be, it would have to be limited to single parameter mechanisms such as CloseHandle();  which, actually, is most of the cases.

2. this one is crucial, the resource release happens when the scope in which the variable is declared is exited.  This means that for inline variables, if the variable is, for example, declared in an "if" compound statement then the resource release occurs at the matching "end" of the compound statement, not at the end of the function or procedure because that doesn't match the variable's scope.

This mechanism is much better than defer() because the programmer has to remember coding the defer.  It also makes refactoring much simpler.

Just food for thought, that's all.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Okoba

  • Hero Member
  • *****
  • Posts: 660
@Fibonacci
5- Good I am waiting :D
6- I asked the dev team, PascalDragon said it is the preference of FPK for unknown reason as far as I remember so it is left that way. It is dangerous as I try to cleanup warnings but these ones, I can not do much cleanly. Please check https://forum.lazarus.freepascal.org/index.php?topic=73286.new;topicseen#new
9- I agree, but I wish for it anyway ;)

Thank you for the other notes.
« Last Edit: May 07, 2026, 02:52:13 pm by Okoba »

Thaddy

  • Hero Member
  • *****
  • Posts: 19165
  • Glad to be alive.
6. There are more ways to get that message. Not only the records, but also e.g. Setlength.
objects are fine constructs. You can even initialize them with constructors.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12345
  • Debugger - SynEdit - and more
    • wiki
6. There are more ways to get that message. Not only the records, but also e.g. Setlength.
And SetLenght may be right, at least in some cases.

If memory serves....
Code: Pascal  [Select][+][-]
  1. function foo: ansistring;
  2. var i: integer;
  3. begin
  4.   i := GetNewLen;
  5.   SetLength(Result, i);
  6.   // ... fill the string with data
  7.   ....

Should give the warning, and the warning is correct.

Result is not guaranteed to be empty in that case. It may be an old string (a temp var of the caller) and have a length of 1GB. And a ref count > 1.
Then if the new length is also around 1GB, the entire data is copied. Though most likely you did not want to keep the old content, and the code will overwrite it.


There may be other cases, where this "side effect" wont happen...

Okoba

  • Hero Member
  • *****
  • Posts: 660
@Martin_fr do you know any side effect for the records?

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12345
  • Debugger - SynEdit - and more
    • wiki
@Martin_fr do you know any side effect for the records?

Nope, but I haven't looked at them in detail. (i.e., how the initialization methods are implemented, and if that is a design or implementation detail).

With dyn-array/string there is a design part, that everyone ignores as "must be as current implementation".

If you have a local declared "var x: ansistring" => then current implementation dictates that it internally is initialized. Otherwise the first statement on it, would go wrong, because it (currently) expects to find a valid ref-count.

As shown, the first bit where that breaks is the "result" var, because it's a temp var of the caller, that is secretly passed in.

But, here is the point, (afaik) the doc explicitly keeps the option for future operatisations to change that for other locals too. DFA could find the first access to a variable, and ignore the refcount. (knowing it has not been set). that would save the dummy nil assign at procedure begin. So that would be a good idea. (except for breaking a lot of code).
If then that first statement is "myDynArryay := nil", that is ok. But if it is "SetLength" then trash is passed to SetLength.
At current pace, I don't expect this for a long time (if ever). But, never say never.

If you have the new managed records, then they have a refcount too. But as I said, I haven't looked at them yet in detail. Which part of it does the initilization set (as per doc?). For a managed record you have: refcount, and data-fields (the latter may itself contain ref-counted data such as strings, and then the above must be satisfied by any init they receive.

 

TinyPortal © 2005-2018