While debugging the String4D code to hunt down a bug in the CompilerSpeedPack, I saw a lot of CopyRecord/FinalizeRecord calls with a try/finally that the compiler generated.
If you have a record with managed fields (string, interface, …) and use it as a function return type the Delphi compiler will change the function into a procedure with an extra var-parameter. So you would think that the compiler treats the result-parameter like a normal var-parameter, but that isn’t the case. The compiler will generate code that guarantees that the result record isn’t changed if the called function throws an exception. For this is adds a temporary record that is used for the function result and then copies it to the target record.
type TMyRec = record Value: string; end; function InternalGetRec: TMyRec; begin Result.Value := 'Hello'; end; function GetRec: TMyRec; begin Result := InternalGetRec; end; procedure Test; var R: TMyRec; begin R := GetRec; end;
The compiler rewrites the “Test” function to:
procedure Test; var R, TempR: TMyRec; begin try GetRec(TempR); CopyRecord(TempR, R, TypeInfo(TMyRec)); finally FinalizeArray([TempR, R], TypeInfo(TMyRec)); end; end;
The same happens if you assign another function’s record result value to your own record result value. The compiler rewrites the “GetRec” function’s code to:
function GetRec: TMyRec; var TempResult: TMyRec; begin try InternalGetRec(TempResult); CopyRecord(TempResult, Result, TypeInfo(TMyRec)); finally FinalizeRecord(TempRecord, TypeInfo(TMyRec)); end; end;
Because the compiler assumes that you may want to use “Result” in the function after the call, it has to guarantee that it is unchanged if an exception is thrown. But if it is the last statement in the function and not secured by an explicit try/finally/except where “Result” is used again, an optimization could be to omit the temporary record, making the code a lot faster.