Can someone explain to me why
Can someone explain to me why
procedure Foo;
begin
with FunctionReturningInterface() do
begin
end;
// more code
end;
releases the interface when the procedure ends and not when the interface goes out of scope, ie when the with ends? After a few years of C++, this annoys me greatly.
procedure Foo;
begin
with FunctionReturningInterface() do
begin
end;
// more code
end;
releases the interface when the procedure ends and not when the interface goes out of scope, ie when the with ends? After a few years of C++, this annoys me greatly.
That's how the compiler works.
ReplyDeleteYes. And all these invisible variables (there may be many) are finalized when the procedure goes out of scope.
ReplyDeleteBecause the compiler generates code like this:
ReplyDeleteprocedure Foo;
var
compilerGeneratedTempVar: IWhatever;
begin
compilerGeneratedTempVar := FunctionReturningInterface();
with compilerGeneratedTempVar do
begin
end;
// more code
end;
You cannot simulate the using keyword from c# in Delphi.
Primož Gabrijelčič Well, is this explicitly stated somewhere? I've tried looking but my googles seem to be failing.
ReplyDeleteAsbjørn Heid I don't know.
ReplyDelete„releases the interface when the procedure ends and not when the interface goes out of scope¯
ReplyDeleteisn't the interface "out of scope" when the callee returns? I mean, in Delphi interfaces are ref counted, in your procedure the count should be 1, yes? when the procedure returns, the ref count is decreased and the count is 0 <- releases the interface
Dorin Duminica There's no way to "reach" the interface when the "with" statement ends, so in that regard it's out of scope there, and what a naive programmer like myself may expect.
ReplyDeleteBut clearly the current implementation moves the scope of the interface to the procedure level.
Primož Gabrijelčič Seems a bit dangerous to rely on otherwise. The compiler/optimizer may just grow up one day...
ReplyDeleteAsbjørn Heid well, declare a local variable of that type, assign to it, then using with MyLocalInterfaceVariable do
ReplyDeleteThe with keyword is nothing more than syntax sugar so it does not introduce anything that changes runtime behavior which is what you expect. It just serves the purpose of not having to create a variable for the expression you are using in your with. And this is exactly what the compiler is doing as explained earlier.
ReplyDeleteI tried looking up a spec on it and couldn't find any, there were vague mentions in passing in Barry Kelly's blog, but that was mostly to explain what the current compiler was doing.
ReplyDeleteAFAICT that's just what the Delphi compiler currently does, but it's not documented anywhere, and could probably evolve.
FWIW in FreePascal and DWScript the scope is similar to C++ (ie. interfaces get released ASAP, and not at the end of the procedure)
Dorin Duminica even with a variable, an optimizing compiler could (should?) detect the variable is no longer used and release the interface.
ReplyDeleteThanks for the input guys. After spending a few years coding primarily in C++, I tend to forget certain "peculiarities" of Delphi, or assume the compiler is a bit smarter than it is :)
ReplyDeleteNested block scope is the same as the scope of the outer block. It is non-lexical intentionally providing the ability to create and set up objects which have the same lifetime as the outer scope. Inner functions CAN have a VAR which you could use, and release.
ReplyDeleteWITH blocks do not imply release, unlike their similarly named brethren in C#. They are syntactic sugar, although their taste is not so sweet for most of us. I would call it syntactic vinegar. I hate With.
procedure Inner;
var
I:ISomething;
begin
I := GetInterface;
with I do begin
end;
I := nil; // release explicitly.
end;
Warren Postma As mentioned before an optimizing compiler should have little trouble realizing the effective scope of the interface, decrementing the reference count once the with block is over.
ReplyDeleteAnyway, since making the OP I remembered that the compiler adds magic when using the "new" for..in syntax, automatically freeing the enumerator if it's an object etc. Would be nice if EMB could add "using" with the similar magic. One could abuse for..in but it would be totally nasty.
Asbjørn Heid wouldn't that be similar to how the hint of the assignment of a variable is not used is generated?
ReplyDeleteWith a new Delphi compiler that plugs into the Clang/LLVM tool ecosystem, a truly lexically scoped and optimized scope rule system might in fact lead to some subtle changes in the behaviour of code like this.
ReplyDeleteWarren Postma That is indeed an exciting prospect.
ReplyDeleteHere's a quick test abusing for..in for scoped transactions.
ReplyDeletevar
qry: TSQLQuery;
begin
for qry in Transaction(SQLQuery1) do
begin
qry.SQL.Text := 'delete from foo';
qry.ExecSQL;
//raise Exception.Create('Oops, time for rollback');
qry.SQL.Text := 'insert into foo (bar) values (42)';
qry.ExecSQL;
end;
end;
Transaction() returns a "fake" enumerator, which starts a transaction when constructed, and performs a commit or rollback in the destructor, depending on wether an unhandled exception was raised inside the for..in block. More code here: http://nopaste.dk/p23101
Not the prettiest I've seen but fun hack :)