Gradually I’ve been using more variants – they can be very useful in certain places for carrying data types that are not known at compile time. One useful value is UnAssigned (‘I’ve not got a value for you’). I think I discovered a long time ago that the function:
function DoSomething : variant;
begin
If SomeBoolean then
Result := 4.5
end;
appeared to be equivalent to:
function DoSomething : variant;
begin
If SomeBoolean then
Result := 4.5
else
Result := Unassigned; // <<<<
end;
I presumed this reasoning that a variant has to be created dynamically and if SomeBoolean was FALSE, the compiler had created it but it was ‘Unassigned’ (<> nil?). To further encourage this thinking, the compiler reports no warning if you omit assigning Result.
Just now I’ve spotted nasty bug where my first example (where ‘Result’ is not explicity defaulted to ‘nil’) actually returned an ‘old’ value from somewhere else.
Should I ALWAYS assign Result (as I do when using prefefined types) when returing a variant?
Yes, you always need to initialize the
Resultof a function, even if it’s a managed type (likestringandVariant). The compiler does generate some code to initialize the future return value of aVariantfunction for you (at least the Delphi 2010 compiler I used for testing purposes does) but the compiler doesn’t guarantee your Result is initialized; This only makes testing more difficult, because you might run into a case where your Result was initialized, base your decisions on that, only to later discover your code is buggy because under certain circumstances the Result wasn’t initialized.From my investigation, I’ve noticed:
Demonstration:
First, here’s the proof that a function returning a
Variantreceives avar Result:hidden parameter. The following two compile to the exact same assembler, shown below:Variant
Next, it’s interesting to see how the call for the two is set up by the complier. Here’s what happens for a call to a procedure that has a
var X:Variantparameter:If we make that “X” a global variable, and we call the function returning a Variant, we get this code:
If you look at the prologue of this function you’ll notice the local variable that’s used as a temporary parameter for the call to
FuncReturningVaris initialized to ZERO. If the function doesn’t contain anyResult :=statements,Xwould be “Uninitialized”. If we call the function again, a DIFFERENT temporary and hidden variable is used! Here’s a bit of sample code to see that:When looking at that code, you’d think the “Result” of a function returning Variant is allways initialized to Unassigned by the calling party. Not true. If in the previous test we make the “X” variable a LOCAL variable (not global), the compiler no longer uses the two separate local temporary variables. So we’ve got two separate cases where the compiler generates different code. In other words, don’t make any assumptions, always assign
Result.My guess about the different behavior: If the
Variantvariable can be accessed outside the current scope, as a global variable (or class field for that matter) would, the compiler generates code that uses the thread-safe @VarCopy function. If the variable is local to the function there are no multi-threading issues so the compiler can take the liberty to make direct assignments (no-longer calling @VarCopy).