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.

Comments

  1. Yes. And all these invisible variables (there may be many) are finalized when the procedure goes out of scope.

    ReplyDelete
  2. Because the compiler generates code like this:

    procedure 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.

    ReplyDelete
  3. Primož Gabrijelčič Well, is this explicitly stated somewhere? I've tried looking but my googles seem to be failing.

    ReplyDelete
  4. „releases the interface when the procedure ends and not when the interface goes out of scope¯
    isn'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

    ReplyDelete
  5. 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.

    But clearly the current implementation moves the scope of the interface to the procedure level.

    ReplyDelete
  6. Primož Gabrijelčič Seems a bit dangerous to rely on otherwise. The compiler/optimizer may just grow up one day...

    ReplyDelete
  7. Asbjørn Heid well, declare a local variable of that type, assign to it, then using with MyLocalInterfaceVariable do

    ReplyDelete
  8. The 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.

    ReplyDelete
  9. I 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.

    AFAICT 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)

    ReplyDelete
  10. Dorin Duminica even with a variable, an optimizing compiler could (should?) detect the variable is no longer used and release the interface.

    ReplyDelete
  11. Thanks 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 :)

    ReplyDelete
  12. Nested 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.
    WITH 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;

    ReplyDelete
  13. 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.

    Anyway, 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.

    ReplyDelete
  14. Asbjørn Heid wouldn't that be similar to how the hint of the assignment of a variable is not used is generated?

    ReplyDelete
  15. With 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.

    ReplyDelete
  16. Warren Postma That is indeed an exciting prospect.

    ReplyDelete
  17. Here's a quick test abusing for..in for scoped transactions.

    var
      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 :)

    ReplyDelete

Post a Comment