Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function returning record with interface field

After asking this question about interface fields in records I assumed that the following would work (notice the assertion):

type
  TRec <T> = record
    Intf : IInterface;
  end;

  TTestClass = class
  public
    function ReturnRec : TRec <Integer>;
  end;

  // Implementation 
  function TTestClass.ReturnRec : TRec <Integer>;
  begin
    Assert (Result.Intf = nil);    // Interface field in record should be initialized!
    Result.Intf := TInterfacedObject.Create;
  end;

I tested this with the following code:

  for I := 1 to 1000 do
    Rec := Test.ReturnRec;

and the assertion fails!

Where's my mistake here? What assumption is wrong?

like image 843
jpfollenius Avatar asked Dec 15 '11 11:12

jpfollenius


1 Answers

Function

function ReturnRec: TRec<Integer>;

Is semantically equal to procedure

procedure ReturnRec(var Result: TRec<Integer>);

[I'm pretty sure that somebody from Embarcadero, probably Barry Kelly or Alan Bauer stated this somewhere but I can't find the reference at the moment.]

In the second case, the compiler assumes that the record will be initialized (if necessary) before it is passed to the ReturnRec and doesn't create any initialization code for rec inside ReturnRec. I'm assuming that the same code path inside compiler is taken for the first example and that's why the Result is not initialized.

Anyway, the solution is simple:

function TTestClass.ReturnRec : TRec <Integer>;
begin
  Result.Intf := TInterfacedObject.Create;
end;

Just assume that compiler knows what it's doing and assign the interface and everything will work just fine.

EDIT

The problem you have occurs from the 'for' loop. Your code

for I := 1 to 1000 do
  Rec := Test.ReturnRec;

is compiled into something like this:

var
  result: TRec<Integer>;

Initialize(result);
for I := 1 to 1000 do begin
  Test.ReturnRec(result);
  rec := result;
end;

That is why you are reusing same record all over and that is why Result.Intf is uninitialized only the first time.

EDIT2

You can trick the compiler by moving t.ReturnRec call out from the loop into a separate method.

procedure GetRec(t: TTest; var rec: TRec);
begin
  rec := t.ReturnRec;
end;

for i := 1 to 1000 do
  GetRec(t, rec);

Now the hidden result variable lives in the GetRec procedure and is initialized every time GetRec is called.

like image 58
gabr Avatar answered Sep 21 '22 10:09

gabr