Saturday, April 25, 2009

Proposal for Automated Variables

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.

No comments: