Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2018-08

Auto vars (again)

8 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Ryan Joseph

I had some free time recently so I decided as a learning experience to fork the compiler and implement the “auto var” idea that was mentioned a few weeks ago.

What I found is that it’s a pretty lightweight (in terms of impact on the compiler) and unintrusive way to manage memory on a per-scope basis which solves a minor but common pattern. This was highly debated but I know from my personal experience I often know at the time of declaring a class that I will allocate the class at the top of the function and free at the end. If this was C++ I would declare the class on the stack but we don’t have that option in Pascal so we’re forced into either a class or record paradigm. The better option would probably be full blown ARC but I don’t know if that’s ever going to be on the agenda of FPC.

I understand there’s lots of potential problems like passing an auto var out of scope, classes with constructors that require parameters (rather big problem) or perhaps the “auto” modifier (I did that because it was easy to parse) but is there any merit to this idea if it was cleaned up and made safe? I did some tests to see if you could prevent passing auto vars out of scope and it’s possible to prevent most ways but not 100%.

========================================================

program auto_test_1;
type
	TMyClass = class
		data: TObject; auto;
	end;
var
	obj: TMyClass; auto;
begin
	// obj is auto so TMyClass.Create is called along with subsequent auto var members
	writeln('obj:', obj.classname);
	writeln('data:', obj.data.classname);
	// when TMyClass.Free is called auto var members call Free also
end.
program auto_test_2;
uses
	fgl;
var
	list: specialize TFPGList<integer>; auto;
	i: integer;
begin
	// list is auto so TFPGList<integer>.Create is called
	// TFPGList as an auto var is a compelling alternative to dynamic arrays
	list.Add(1);
	list.Add(2);
	list.Add(3);
	for i in list do
		writeln(i);
end.

Regards,
Ryan Joseph

Maciej Izak
to Ryan Joseph

2018-08-18 1:24 GMT+02:00 Ryan Joseph :

but is there any merit to this idea if it was cleaned up and made safe? I did some tests to see if you could prevent passing auto vars out of scope and it’s possible to prevent most ways but not 100%.

You are trying to achieve something what is almost possible with current FPC trunk version (which is also safe in the case of exception):

=== code begin ===
  TAutoCreate<T: class> = record
    _: T;
    class operator Initialize(var a: TAutoCreate<T>);
    class operator Finalize(var a: TAutoCreate<T>);
    class operator Copy(constref a: TAutoCreate<T>; var b: TAutoCreate<T>);
    class operator AddRef(var a: TAutoCreate<T>);
  end;
class operator TAutoCreate<T>.Initialize(var a: TAutoCreate<T>);
begin
  a._ := T.Create;
end;
class operator TAutoCreate<T>.Finalize(var a: TAutoCreate<T>);
begin
  a._.Free;
end;
class operator TAutoCreate<T>.Copy(constref a: TAutoCreate<T>;
  var b: TAutoCreate<T>);
begin
  // raise { some exception - prevent copy };
end;
class operator TAutoCreate<T>.AddRef(var a: TAutoCreate<T>);
begin
  // raise { some exception - prevent copy };
end;
var
  o: TAutoCreate<TObject>;
begin // auto Create
  Writeln(o._.ToString);
end. // auto Free;
=== code end ===

the code above was not tested (written now) but should work. The only disadvantage of above solution is lack of the way to omit "_".

The solution which probably should be accepted by fpc team is default property without indexer:

=== code begin ===
property obj: T read fobj; default;
=== code end ===

with this property should be possible:

=== code begin ===
Writeln(o.ToString);
=== code end ===

next you can try to provide compiler magic:

=== code begin ===
var o: TObject auto; // equivalent of var o: TAutoCreate<TObject>;
// please note that syntax with semicolon is improper:
//var o: TObject; auto;
=== code end ===

anyway I am not big fan of "auto" because it means many troubles, what if:

* there is no parameter less constructor
* why to call constructor when there is NewInstance (this seems more proper for this case but...)

after NewInstance you can (and rather this will be necessary) call selected constructor from instance which will be executed like regular method, but what is the sense of "auto" calling NewInstance when you still need to execute Constructor? :D

I see rather no reason for this feature in compiler (but nice try :) ). There are many other directions to solve "auto free" problems without compiler modifications - maybe not all can be solved but many problems for sure - for example you have ready to use TAutoFree from mORMot or initial experiments with ARC objects for NewPascal (still worth to mention ;P).

Best regards, Maciej Izak
Ryan Joseph
to Maciej Izak
Show quoted text 15 lines
On Aug 17, 2018, at 7:50 PM, Maciej Izak wrote: 2018-08-18 1:24 GMT+02:00 Ryan Joseph : but is there any merit to this idea if it was cleaned up and made safe? I did some tests to see if you could prevent passing auto vars out of scope and it’s possible to prevent most ways but not 100%. You are trying to achieve something what is almost possible with current FPC trunk version (which is also safe in the case of exception): var o: TAutoCreate<TObject>; begin // auto Create Writeln(o._.ToString); end. // auto Free; === code end === the code above was not tested (written now) but should work. The only disadvantage of above solution is lack of the way to omit "_”.

I think you may be right about this. Having “auto” leverage the existing management operators makes sense. It’s more overhead to wrap everything in records though and the larger problem is the so-called syntactic sugar missing.

Show quoted text 12 lines
The solution which probably should be accepted by fpc team is default property without indexer: === code begin === property obj: T read fobj; default; === code end === with this property should be possible: === code begin === Writeln(o.ToString); === code end ===

How does that property work exactly? Making this work properly is the most important part but I never considered it. It sounds like “with” statements kind of.

Show quoted text 8 lines
next you can try to provide compiler magic: === code begin === var o: TObject auto; // equivalent of var o: TAutoCreate<TObject>; // please note that syntax with semicolon is improper: //var o: TObject; auto; === code end ===

I wasn’t sure what’s proper. Function modifiers look like that but there are no var modifiers in Pascal as of now. The absolute keyword is kind of similar. We have “threadvar” also which sets a precedence and starts a new section which is nice. Either way I really like the idea of introducing a keyword instead of using the generic syntax.

anyway I am not big fan of "auto" because it means many troubles, what if: * there is no parameter less constructor

That’s the biggest problem IMO. Calling NewInstance is correct, you’re right, but if the class needs parameters you need to call the constructor again anyways so it kind of defeats the purpose. When the class has only a parameterless constructor it works beautifully though so the feature is limited to those cases.

* why to call constructor when there is NewInstance (this seems more proper for this case but...) after NewInstance you can (and rather this will be necessary) call selected constructor from instance which will be executed like regular method, but what is the sense of "auto" calling NewInstance when you still need to execute Constructor? :D

Agreed, that limits the usefulness of the feature. When it works well it’s a real win though.

I see rather no reason for this feature in compiler (but nice try :) ). There are many other directions to solve "auto free" problems without compiler modifications - maybe not all can be solved but many problems for sure - for example you have ready to use TAutoFree from mORMot or initial experiments with ARC objects for NewPascal (still worth to mention ;P).

How does TAutoFree in mORMot work? Never heard of this.

Full blown ARC is probably the best option but I was thinking of intermediate steps that are easy to implement and solve a a portion of the common use cases. It’s a poor mans ARC at best. ;)

Regards,
Ryan Joseph

Ryan Joseph
to Maciej Izak: 2018-08-18 1:24 GMT+02:00 Ryan Joseph : You are trying to achieve something what is…
Show quoted text 5 lines
On Aug 17, 2018, at 7:50 PM, Maciej Izak wrote: class operator Initialize(var a: TAutoCreate<T>); class operator Finalize(var a: TAutoCreate<T>);

One other thing I wanted to ask you. I know it’s minor but for me it’s pretty disappointing that the Initialize and Finalize operators aren’t simply constructor/destructor since those names are available in records already (constructor with no parameter that is).

I say this because the syntax is long and hard to remember and don’t follow the usual convention of constructors/destructors. Initialize and Finalize are the most common operators you implement so it makes sense they would follow the normal syntax rules (c++ does this for classes declared on the stack).

Was there any consideration to making the default constructor and destructor be used instead of class operators?

Regards,
Ryan Joseph

Maciej Izak
to Marcos Douglas B. Santos: See http://blog.synopse.info/post/2014/11/14/Automatic-TSQLRecord-memory-handling Marcos…

2018-08-18 20:26 GMT+02:00 Marcos Douglas B. Santos :

Show quoted text 7 lines
On Sat, Aug 18, 2018 at 2:04 PM, Ryan Joseph wrote: > > How does TAutoFree in mORMot work? Never heard of this. See http://blog.synopse.info/post/2014/11/14/Automatic- TSQLRecord-memory-handling

worth to note that in FPC you additionally need to use "with ... do" (or use local variable IAutoFree), otherwise interface will be release immediately and code will be broken. The example in blog needs to be corrected:

=== code begin ===
function NewMaleBaby(Client: TSQLRest; const Name,Address: RawUTF8): TID;
var Baby: TSQLBaby;
begin
  with TSQLBaby.AutoFree(Baby) do begin
    Baby.Name := Name;
    Baby.Address := Address;
    Baby.BirthDate := Date;
    Baby.Sex := sMale;
    result := Client.Add(Baby);
  end;
end;
=== code end ===

or you need to wait for {$MODESWITCH SCOPEDINTERFACEDESTROY} in NewPascal to use examples AS-IS (with SCOPEDINTERFACEDESTROY on). To be honest I forgot to send the patch with SCOPEDINTERFACEDESTROY to FPC when I had occasion/access to FPC trunk ^^ - anyway I was the only one who want this in the FPC core team for Delphi compatibility (and for large legacy projects where is really hard to adjust code which depends on SCOPEDINTERFACEDESTROY)... FPC core team said that this behavior of Delphi is not documented and not COM compatible (or is undefined in COM - I don't remember). Also there is some mystery example for large methods when Delphi is able to break the rule and such interface is released earlier (but no one was able to provide this example) - I was also trying without success, so SCOPEDINTERFACEDESTROY seems really Delphi compatible.

Best regards, Maciej Izak
Ryan Joseph
to Ryan Joseph: I think you may be right about this. Having “auto” leverage the existing management…
Show quoted text 15 lines
On Aug 18, 2018, at 11:04 AM, Ryan Joseph wrote: > The solution which probably should be accepted by fpc team is default property without indexer: > > === code begin === > property obj: T read fobj; default; > === code end === > > with this property should be possible: > > === code begin === > Writeln(o.ToString); > === code end === How does that property work exactly? Making this work properly is the most important part but I never considered it. It sounds like “with” statements kind of.

Ok, I understand what you mean now. This is actually a really great idea for all sorts of other uses because it helps to unify namespaces between classes and prevent long.and.annoying() chaining of fields.

Btw why should this be a property? I know [] properties use the default keyword also but I don’t understand where this came from and it’s strange to name the [] property since the name is irrelevant. I would think you’d want to just put a modifier on the default field:

 TAutoCreate<T: class> = record
    _: T; default;
    class operator Initialize(var a: TAutoCreate<T>);
    class operator Finalize(var a: TAutoCreate<T>);
    class operator Copy(constref a: TAutoCreate<T>; var b: TAutoCreate<T>);
    class operator AddRef(var a: TAutoCreate<T>);
  end;

just curious and I thought I’d ask.

Regards,
Ryan Joseph

Jonas Maebe
to Maciej Izak: 2018-08-18 20:26 GMT+02:00 Marcos Douglas B. Santos : worth to note that in FPC you…
Maciej Izak wrote 5 lines
on SCOPEDINTERFACEDESTROY)... FPC core team said that this behavior of Delphi is not documented and not COM compatible (or is undefined in COM - I don't remember). Also there is some mystery example for large methods when Delphi is able to break the rule and such interface is released earlier (but no one was able to provide this example)

Here is one: https://bugs.freepascal.org/view.php?id=6035#c28656

Jonas