Proposal for Automated Variables

Or: “Environmentally Friendly Coding – Recycle Your Keywords

Yesterday I logged a Quality Central report proposing the addition of support for “automatic variables” to the Delphi language.  Not only is it an excellent idea (in my humble and utterly objective opinion :)), but there is already a keyword in the language that could be co-opted for this purpose, a keyword that has been at something of a loose-end since it was deprecated (rendered obsolete even) a long, long time ago…

The language keyword in question is automated.  This was introduced in Delphi 2.0 as part of the initial implementation to support COM automation and, if memory serves, deprecated in the very next release when “proper” (albeit COM) interfaces were added to the language.

The functionality that AutoFree() provides is similar to the concept of an auto pointer – a specific variant of the general concept of a smart pointer.  “auto”… “automatic”… “automated”… the similarity in the terms, and the relevance of the semantics, is striking.  To me at least.

The proposal in Quality Central drew inspiration directly from exchanges in the comments on my post on an AutoFree() implementation and a realisation that the required behaviour is very similar to that already implemented for interface references – it would not be entirely alien to the Delphi language.

Indeed I believe it would be quite easily understood and welcomed by most, if not all, developers.

The Proposal

The automated keyword should be supported as a decoration on variable declarations.  That is local variables, member variables and unit variables:

interface

  type
    TFoo = class
    private
      fBar: TBar automated;
    end;

implementation

  var
    _Bar: TBar automated;

  function FooFn;
  var
    bar: TBar automated;
  begin
    :
  end;

The rules for the keyword and the effect of it shall be as follows:

- The automated keyword shall be valid only for pointer and object reference type variables. (*)

- When marked as automated the compiler shall emit code to initialize a variable to NIL. This already occurs for local variables of certain types, most notably interface references, as well as all member variables (albeit indirectly in that case), and currently has to be specified directly, if required, for unit variables.

- When marked as automated the compiler shall emit code to finalize a variable in a manner appropriate to it’s type.  For object references this shall be a call to Free; for pointers a call to FreeMem()This is directly equivalent to the code already emitted by the compiler to finalize interface references by calling Release().

- automated would not be combinable with absolute.

(*) – it could also be supported on record types with the proviso that the record type in question supports a parameterless constructor (to be called to initialize the record) and a lone destructor (called to finalize the record).  But to keep things simple lets stick to object references and pointers, for now at least.

The effect on code of the use of this keyword would be to facilitate:

1. resource protection for temporary objects held in local variables without the need for try..finally blocks.

2. reliable clean up of dependent objects in object hierarchies without the need for objects to implement a destructor (solely) to free those dependent objects.  Destructors may still be necessary for other purposes of course.

3. reliable clean up of unit (a.k.a “global”) objects without the need for a unit finalization.  Again, finalization may still be required for other purposes.

Note however that it would not prevent these existing techniques from functioning, if required or preferred.

There is only one possible danger that I foresee, which is that a developer might mark a variable as automated but then dispose of the referenced object/memory explicitly without re-initializing the variable.  e.g.:

  procedure SomeFn;
  var
    bar: TBar automated;
  begin
    bar := TBar.Create;
    bar.Free;
  end;

In this case, when SomeFn exits, the finalization of bar will likely result in an error since bar has been left holding a reference to an object that has already been Free‘d.

Note that the initialization of bar as NIL (as a consequence of being automated) specifically avoids any problem if bar is only assigned a reference conditionally in the code.

Note also that explicitly disposing an automated variable is not in and of itself problematic, as long as the variable is also then explicitly re-set to NIL.  In the above example, if bar had been NIL‘d once freed, or FreeAndNIL() had been used, then there would not be any problem with the automated behaviour of bar.

The code below illustrates safe explicit disposal and potentially conditional assignment of an automated reference:

  procedure SomeFn;
  var
    bar: TBar automated;
  begin
    bar := TBar.Create;

    // do some work with "bar"

    FreeAndNIL(bar);

    if SomeCondition then
    begin
      bar := TBar.Create;
      // do more work with a new "bar"
    end;
  end;

This code is perfectly safe, will not result in a runtime error and will not leak a TBar.

The potential dangers and pitfalls of an automated variable behaviour implemented as described are actually no different to the potential dangers and pitfalls associated with the manual techniques that it could replace.

I should also mention that I cannot see that the proposal described here would necessarily interfere with, or be interfered with by, the existing, deprecated usage of the automated keyword.

A final observation is that this implementation “feels very Pascal’ly” to me.  In a good way.

Call To Action

The Quality Central report # is 67324.

If you feel the idea has merit please vote for it.

If you feel it needs refining, comment on it (in QC, rather than here).

Tags: ,

29 comments

  1. Jouni Aro’s avatar

    Great idea, Jolyon. Voted!

  2. El Cy’s avatar

    +1

    Maybe will be a good idea to post it also in to the non-tech CG forum.

  3. Lars Fosdal’s avatar

    How do we determine correct vs incorrect use of automated?
    How will SomeObj behave in the examples below?

    Ex.1
    var
    __SomeObj : TObjType; automated;
    begin
    __SomeObj := TSomeObj.Create;
    // set some props
    __SomeContainer.Add(SomeObj);
    end;

    Ex.2
    var
    __SomeObj : TObjType; automated;
    begin
    __SomeObj := SomeContainer.Lookup(Key);
    // set some props
    end;

  4. El Cy’s avatar

    Since I’m a fun of the idea, I’ still wondering for more complex cases (object graphs with automated objects inter-dependencies) if the order of automatic “destruction” will have any impact …

    For simple cases: automated will be just some sort of syntax sugar that will hide the fact that the compiler will inject the actual Free calls in Finalization (for unit vars), at frame exit (for procedures/functions) and actual owner destructors (for members) …

    This will not be a real GC-ed solution, but it will behave (partially) like one !

  5. m. Th.’s avatar

    Someone else had this idea ‘a long way in time’ before. :-) (no, it isn’t mine)

    See
    http://delphi.wikia.com/wiki/Delphi_Suggestions_-_Compiler
    (look for Automatic Create/Free down to page – nice discussion there pointing other pitfalls for this)

    Also, see Ralf Stocker’s QC#32076

    A more clearer and safer solution would be, perhaps, in-line var/’using’ which will limit also the scope of the object. (QC #63369)

    Just an aside, why don’t you discuss this in a newsgroup? It’s a better environment there (more eyes, threaded layout, easy comment notification, no blog engine quirks (for code posting, angled braces) aso.

    Just my2c,

    m. Th.

  6. Jolyon Smith’s avatar

    @ M Th: my proposal is somewhat different to that which you linked to. In that case the idea was that an “auto” variable would not only be automatically disposed of, but also automatically created.

    “Using” might be an option, however it has often been mentioned that it is not desirable to introduce new keywords – especially reserved words – unless absolutely necessary. This approach recycles an existing keyword.

    Note also however that “using” cannot address member variables and isn’t really much help with unit variables (although these are much less common, I know) – “using” is basically an alternative to AutoFree(), not this re-use of “automated”.

    @Lars: the behaviour of automated in those cases is exactly the same as if the developer was taking care of the disposal explicitly. A developer might incorrectly use automated just as they might use try..finally incorrectly.

    And of course, depending on the contracts for those examples, the automated variable might actually be needed after all (in which case a try..finally would be needed, in the absence of “automated”).

    @El Cy: Yes, order of destruction could be important, but that order would presumably be defined by the implementation, presumably some basis on declaration order would be involved.

    If the implementation defined order of destruction creates a problem in any specific case, the developer always has the option of not using automated on those problem cases and taking care of the required orderly disposal themselves.

    And even if the variables they explicitly dispose are marked automated, if they Free and NIL them (as they should anyway), the automated disposal is then a NO-OP.

    Again, it’s not really any different to a case where currently explicit disposal code has some order dependency – the developer still has to introduce the required order to correctly to address those dependencies.

    And yes, I shall post to the newsgroups, but probably direct people here for the details. I’m paying handsomely for my traffic allowance, I might as well use it!

    :)

  7. Sergey Antonov aka oxffff’s avatar

    What about the next sample?
    procedure SomeFn;
    var
    bar1: TBar automated;
    bar2: TBar automated;
    c:Tobject;
    begin
    bar1 := TBar.Create;
    bar2: =bar1;

    //hidden finalization
    bar1.Free;
    bar2.Free;
    end;

    May be just a form of C# Using keyword?

    var
    bar1: TBar
    bar2: TBar
    c:Tobject;
    begin
    Using bar1 := TBar.Create;
    begin
    bar2: =bar1;
    ..
    end;
    end;

  8. Jolyon Smith’s avatar

    @Sergey: A somewhat contrived example, and one more likely to arise I think without “automated” rather than with it, since (again) this is absolutely no different to a developer making the same mistake with try finally:

    procedure SomeFn;
    var
    bar1: TBar;
    bar2: TBar;
    begin
    bar2 := NIL;
    bar1 := TBar.Create;
    try
    // code passes..
    bar2: =bar1;
    // more code passes..
    finally
    bar1.Free;
    bar2.Free;
    end;
    end;

    You can’t stop developers making dumb mistakes. But I’d suggest that if we remove “noise” from the code, it could make it less likely that you’ll make those mistakes in the first place, and it will certainly make it easier to *spot* them.

    (And note that if you are using FullDebugMode with FastMM – you are, aren’t you? for dev/test/debug builds at least – you should get an “attempt to free an already freed object” error in this case)

    And as I mentioned previously, “using” only applies to temporary, local objects.

    If we had “using” we’d still want something else to deal with member variables and (less so) unit variables.

    With “automated”, you possibly don’t actually need “using” at all.

  9. Lars Fosdal’s avatar

    I am slightly uncomfortable with this construct. It differs in behaviour from strings, dynamic arrays and interfaces which release on scope as well on explicit free (SetLength or set to nil), by the use of reference counting.

    If I use an automated object reference, I am having problems seeing how I can work with that variable and pass it into another object graph for keeping, unless there some sort of reference counting so that I don’t accidentally auto-free an object which I have passed on.

  10. Lars Fosdal’s avatar

    Addendum: The second paragraph above is in context of changing the automated behaviour to be consistant with dyn.arrays and interfaces, ie free on reassign/scope/explicit free.

  11. Jolyon Smith’s avatar

    Hi Lars,

    Yes it does differ from those things, primarily because object references are themselves different.

    It’s important not to confuse the proposal as being a suggestion for a general purpose garbage collector. It’s simply intended to alleviate the tedious boilerplate that is otherwise needed with manual disposal of object references.

    As far as accidentally auto-freeing an object which you have passed on, it is absolutely no different from accidentally manual-freeing an object which you have passed on.

    You can make this mistake, and all the other mistakes that might be possible, today:

    list := TList.Create;
    try
    // code passes

    SomeFn(list);

    // code passes
    finally
    list.Free;
    end;

    The only difference is that you get the try..finally behaviour without having to create all that boiler plate. The behaviour is still apparent (perhaps even more so, since it isn’t potentially obscured by other unrelated boilerplate) from the fact that “list” is visibly marked as “automated”.

  12. naf’s avatar

    This is a good idea.

    Self aborption warning: I also suggested an AUTO directive:

    http://nafdb.blogspot.com/2007/04/first-screenshots-of-delphi-2010-for.html

    :-)

  13. naf’s avatar

    Er, “Self Absorption” was intended rather than “Self Abortion” ;-)

  14. Jolyon Smith’s avatar

    Hi naf,

    I don’t know if yours was the suggestion that “m Th” linked to, although it looks similar. An important difference is that I’m envisaging an auto_ptr type implementation – i.e. all about disposal, and not dealing with object creation at all, which I would still leave in the hands of the developer.

    Auto-creation poses difficulties for classes that don’t have meaningful parameterless constructors, for example, and could be tricky when extended to member variables where you may want automatic disposal, but only conditional creation – e.g. objects that are created only on-demand, via a property getter.

  15. Maxim’s avatar

    Hi.

    What about

    whith a := TSomething.Create() do
    begin


    end

    or to avoide type inference:

    with a: TSomething = TSomethig.Create() do…

    And all this with autofreeing when out of scope.

  16. Jolyon Smith’s avatar

    Hi Maxim – again, this only deals with temporary objects in local variables.

    “automated” would take care of that *and* also be of use for member variables and unit variables.

  17. stevie’s avatar

    Hi,

    I think a better syntax might be:

    instead of:

    var
    __SomeObj : TObjType; automated;

    rather use:

    automated var
    __SomeObj : TObjType;
    another_obj: TMyObject; //also automated
    var
    RegVar : TMyOtherObject; //non-automated

    we already have a threadvar, so this is another var block type…

  18. stevie’s avatar

    One other thing one must be careful of is, that by looking at the variable one can’t easily determine how it is destroyed/freed (automatic or not) which can easily lead to programming errors or forgetting to free normal objects that the programmer might have assumed is auto freed, etc.

    The language must not make the programmer guess how a variable works or does, the code / variable must be able to say as much about itself as possible on it’s own (for example interfaces have an ‘I’ prefix (albeit optional, Types a ‘T’, etc.) but all this makes the code readable by others way after it’s written and that’s what the spirit of Pascal / Delphi is all about.

    One idea might be and IDE feature that can super-impose a tiny glyph (like a tiny “A”) wherever a variable is used to indicate it’s type & certain features, but must be always visible, so just reading or scanning the code immediately gives one an indication of the functionality of a variable.

    An integer would have a tiny “int” glyph in the bottom corner of every integer, a tiny “str” glyph for strings, etc. Totally removing need for hungarian notation and the like (readable variables are still important though :)
    Maybe this can be something tools like Castalia could tackle.

    Another useless 0.02cents :)

  19. Jolyon Smith’s avatar

    Hi Stevie – As regards an “automated” block type, rather than a variable decoration, I did consider that, but is creates a problem with the existing (albeit deprecated) use of the keyword as a block type in class declarations.

    As regards your other point, there is no guess work. The “automated” keyword is as explicit about the behaviour of that variable as “absolute” is, or “abstract” is for method declarations (or “empty” in Oxygene, for example).

    You could achieve the same things by being more explicit (*) and then seeing the behaviour expressed in the code (or by seeing it NOT expressed, in the case of an empty/abstract method – :) ), but the declaration encapsulates it for you, allowing the code to be free of mundane housekeeping jobs.

    (*) e.g. in the case of “absolute”:

    procedure TSomeForm.OnControlEnter(Sender: TObject);
    var
    listbox: TListbox absolute Sender;
    edit: TEdit absolute Sender;

    begin
    if Sender is TListbox then
    listbox.Color := …
    else if Sender is TEdit then
    edit.Color := …

    end;

    vs

    procedure TSomeForm.OnControlEnter(Sender: TObject);
    var
    listbox: TListbox;
    edit: TEdit;

    begin
    if Sender is TListbox then
    begin
    listbox := TListbox(Sender);
    listbox.Color := …
    end
    else if Sender is TEdit then
    begin
    edit := TEdit(Sender);
    edit.Color := …
    end

    end;

    i.e. with “absolute” you still have to exercise exactly the same cautions that you have to exercise when achieving the same thing “the long way around” – type checking etc – but as long as you are doing that, the actual implementation details you are protecting by exercising that caution are taken care of for you.

    This isn’t quite the same, as this “long hand” way is not directly comparable to, and is slightly less efficient than, the language supported capability. The directly comparable “long hand” implementation would be even more error prone as it would involve using pointers which can easily trip up even the most cautious developer.

    “automated” on the other hand is directly comparable to existing “long hand” techniques but it also protects the developer from incorrect use of those techniques.

    e.g. I still quite often see try..finally being incorrectly used:

    try
    list := TList.Create;
    // …
    finally
    list.Free;
    end;

    The developer has understood what they need to do, but hasn’t grasped the finer points of the runtime behaviour of exceptions and constructors and learned to apply that (or perhaps carelessly forgot in this one instance, which perhaps involve multipled temp objects, where the try..finally’s can get messy).

    Had they applied the same intention by marking “list” as automated, those details would have been take care of.

    And all those 2 cents add up to a useful contribution in the end.

    ;)

  20. Nigel X’s avatar

    Like your suggestion, Jolyon,

    In the meantime i’d be happy enough for some common objects that automatically clean up after themselves, e.g. TSafeStringList, TSafeList, TSafeBitmap, etc.

    TSafeStringList alone would save me about 5000 lines of code each day ;-)

  21. Serg’s avatar

    I don’t like the idea at all. The feature proposed just makes your code more prone to errors.

  22. Jolyon Smith’s avatar

    Hi Nigel – well, in the meantime you might want to check out the post on AutoFree() (“Free Yourself”) that led me to this suggestion in the first place.

    http://www.deltics.co.nz/blog/?p=391

    :)

  23. Jolyon Smith’s avatar

    @Serg: imho demonstrably *less* prone, when used correctly (see previous comments about incorrect use of try..finally, although presumably you don’t use try..finally, since the same [and more] errors can occur with that as are possible with “automated”).

    ;)

    But if you don’t like it you don’t have to vote for it, and even if it’s implemented, you wouldn’t have to use it.

    :)

  24. Geza’s avatar

    And intermediate solution would be an optional finally keyword in blocks and variable assignments for definition:

    procedure foo;
    var
    list : TList := nil
    begin
    [...]
    finally
    list.free;
    end;

    or even better:

    procedure foo;
    var
    list : TList := TList.Create;
    begin
    [...]
    finally
    list.free
    end;

  25. Geza’s avatar

    Here is another variant:

    procedure foo;
    var
    list : TList := TList.Create & finally free;
    begin
    [...]
    end;

  26. Geza’s avatar

    And an other one: ;-) I finish with this one…

    procedure foo;
    var
    list : TList.Create & finally free;
    begin
    [...]
    end;

  27. Daniel Lehmann’s avatar

    I don’t like this feature THAT much. In my opinion it hides a broken system (the need for manual destruction) behind a new syntax and will make the transition to a GC’ed native Delphi more difficult (and I hope that we one day get a GC’ed native Delphi).

  28. Jolyon Smith’s avatar

    Hi Daniel.

    Manual resource management is not a “broken” system. It’s just a different one. One that can be more efficient than automatic garbage collection.

    But I don’t see how it would make any transition to a fully auto GC’d Delphi more difficult. Far from it.

    With the “automated” keyword, an auto-GC’d Delphi compiler would simply ignore the keyword.

    WITHOUT the “automated” keyword the compiler would still be required to compile the explicit manual resource management code (indistinguishable to the compiler from any other compilable code), so the RTL would need to create facade support for certain operations that would be NO-OPs at runtime.

    This was one of the *mistakes* (imho) that was made with Delphi.NET.

    So if anything, an “automated” keyword would be a useful step toward GC.

    Not-with-standing the fact that I believe that a GC in (native code) Delphi would be a mistake.

    If you want to write with a GC, just use .NET.

  29. Marco van de Voort’s avatar

    besides that, there are other reasons for manual allocation besides speed, namely predictability, and a guarantee that resources won’t be kept longer than specified (at least from the viewpoint of the program, the heapmanager is something altogether different).

    IMHO proposals like this are a hack. If you want automatic resources that bad, use a language designed for it.

Comments are now closed.