9

I need a clarification of this case.

According my tests the Result variable is defined to: Boolean=False, Integer=0, String='', Object=nil etc from the first line. But I have never seen an official reference for this. It also make sense as this gives the hint.

[DCC Warning] Unit1.pas(35): H2077 Value assigned to 'TForm1.Test' never used

function TForm1.Test: Boolean;
begin
  Result := False;
  // Some arbitrary code here

  Result := True;
end;

But what happens if I comment out the first line and there is an exception somewhere before last line? Is Result = False ?

If Result is undefined this means that I always have to start every function by defining Result in case of exception later. And this make no sense for me.

Roland Bengtsson
  • 5,058
  • 9
  • 58
  • 99

3 Answers3

16

As stated by the official Delphi documentation, the result is either:

  • CPU register(s) (AL / AX / EAX / RAX / EAX:EDX) for ordinal values and elements contained in a register;
  • FPU register (st(0) / XMM1);
  • An additional variable passed as a latest parameter.

The general rule is that no result value is defined by default. You'll have to set it. The compiler will warn you about any missing result set.

For a string, dynamic array, method pointer, or variant result, the effects are the same as if the function result were declared as an additional var parameter following the declared parameters. In other words, the caller passes an additional 32-bit pointer that points to a variable in which to return the function result.

To be accurate, the var parameter is not only for managed types, but only for record or object results, which are allocated on the stack before calling, so are subject to the same behavior.

That is, for instance, if your result is a string, it will passed as an additional var parameter. So it will contain by default the value before the call. It will be '' at first, then if you call the function several times, it will contain the previous value.

function GetString: string;
// is compiled as procedure GetString(var result: string);
begin
  if result='' then
    result := 'test' else
    writeln('result=',result);
end;

function GetRaise: string;
// is compiled as procedure GetRaise(var result: string);
begin
  result := 'toto';
  raise Exception.Create('Problem');
end;

var s: string;
begin
  // here s=''
  s := GetString; // called as GetString(s);
  // here s='test'
  s := GetString; // called as GetString(s);
  // will write 'result=test' on the console
  try
    s := GetRaise; // called as GetRaise(s);
  finally
    // here s='toto'
  end;
end;

So my advices are:

  • Fix all compiler warning about unset result;
  • Do not assume that a result string is initialized to '' (it may be at first, but not at 2nd call) - this is passed as a var parameter, not as a out parameter;
  • Any exception will be processed as usual, that is, the running flow will jump to the next finally or except block - but if you have a result transmitted as a var parameter, and something has been already assigned to result, the value will be set;
  • It is not because in most cases, an unset result ordinal value (e.g. a boolean) is 0 (because EAX=0 in asm code just before the return), that it will be next time (I've seen random issues on customer side because of such unset result variables: it works most time, then sometimes code fails...);
  • You can use the exit() syntax to return a value, on newer versions of Delphi.
Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
10

You state:

If Result is undefined this means that I always have to start every function by defining Result in case of exception later.

You are concerned that the return value of a function is undefined if the function raises an exception. But that should not matter. Consider the following code:

x := fn();

If the body of the function fn raises an exception then, back at the call site, x should not be assigned to. Logically the one-liner above can be thought of as a two-liner:

  1. call fn()
  2. assign return value to x

If an exception is raised in line 1 then line 2 never happens and x should never be assigned to.

So, if an exception is raised before you have assigned to Result then that is simply not a problem because a function's return value should never be used if the function raises an exception.


What you should in fact be concerned about is a related issue. What if you assign to Result and then an exception is raised? Is it possible for the value you assigned to Result to propagate outside of the function? Sadly the answer is yes.

For many result types (e.g. Integer, Boolean etc.) the value you assign to Result does not propagate outside the function if that function raises an exception. So far, so good.

But for some result types (strings, dynamic arrays, interface references, variants etc.) there is an implementation detail that complicates matters. The return value is passed to the function as a var parameter. And it turns out that you can initialise the return value from outside the function. Like this:

s := 'my string';
s := fn();

When the body of fn begins execution, Result has the value 'my string'. It is as if fn is declared like this:

procedure fn(var Result: string);

And this means that you can assign to the Result variable and see modifications at the call site, even if your function subsequently raises an exception. There is no clean way to work around it. The best you can do is to assign to a local variable in the function and only assign Result as the final act of the function.

function fn: string;
var
  s: string;
begin
  s := ...
  ... blah blah, maybe raise exception
  Result := s;
end;

The lack of a C style return statement is felt strongly here.


It is surprising hard to state accurately which type of result variables will be susceptible to the problem described above. Initially I thought that the problem just affected managed types. But Arnaud states in a comment that records and objects are affected too. Well, that is true if the record or object is stack allocated. If it is a global variable, or heap allocated (e.g. member of a class) then the compiler treats it differently. For heap allocated records, an implicit stack allocated variable is used to return the function result. Only when the function returns is this copied to the heap allocated variable. So the value to which you assign the function result variable at the call site affects the semantics of the function itself!


In my opinion this is all a very clear illustration of why it was a dreadful mistake, in the language design, for function return values to have var semantics as opposed to having out semantics.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • "In my opinion this is a very clear illustration of why it is a mistake, in the language design, for function return values to have var semantics as opposed to having out semantics." This is my opinion too - I suspect this has been done in first verison of Delphi, for speed and code size reasons - an `out` parameter has to be initialized by caller. At this time, code size did matter (in a 16 bit world). Now, for compatibility reasons, even if .exe size does not matter any more (a void Delphi app is huge), we are stick to the same semantic... :( – Arnaud Bouchez Apr 10 '12 at 07:41
  • @Arnaud My guess is that it only became an issue when managed types arrived, e.g. Delphi 2 long strings – David Heffernan Apr 10 '12 at 07:55
  • You're right: long strings where added in Delphi 2... but `shortstring` returned variables were already passed as `var` in Delphi 1. This is a reminiscence from TP + Delphi 1 years, about `string=shortstring` "good old" time, I suspect. The `shortstring` result of a function was allocated on caller stack, without initialization. And it is the same for the `string` managed and Unicode type until Delphi XE2. ;) – Arnaud Bouchez Apr 10 '12 at 08:39
  • @ArnaudBouchez Ah, shortstring. So yes this goes a long way back, pre-Delphi even. It's really looking like a very poor decision now. – David Heffernan Apr 10 '12 at 08:50
  • To be accurate, the `var` parameter is not only for managed types, but only for `record` or `object`results, which are allocated on the stack before calling, so are subject to the same behavior. – Arnaud Bouchez Apr 10 '12 at 09:38
  • @ArnaudBouchez It's even more complicated than that! What you say is true for local variables. But not true for globals or object member fields. – David Heffernan Apr 10 '12 at 09:50
  • Of course a global `string` or `record` - which are not to be used - globals are evil! :) - are allocated not on the stack, but at program startup. But they are allocated before calling, and their content is shared among calls, just like a regular `var` parameter - this was the point here. – Arnaud Bouchez Apr 10 '12 at 13:25
  • @Arnaud No, that's not the case. When assigning globals and class members, a hidden implicit temporary local is passed by var to the function. Only when the function returns does it get transferred to the global or class member. If the target of the function assignment is a local variable, then that local variable is passed by reference. The semantics are determined by the calling code! – David Heffernan Apr 10 '12 at 13:28
  • So this is even worse than I expected, about the performance POV. – Arnaud Bouchez Apr 10 '12 at 15:26
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/9918/discussion-between-arnaud-bouchez-and-david-heffernan) – Arnaud Bouchez Apr 10 '12 at 15:26
6

No, Result has no (guaranteed) default value. It is undefined unless you give it a value. This is implied by the documentation, which states

If the function exits without assigning a value to Result or the function name, then the function's return value is undefined.

I just tried

function test: integer;
begin
  ShowMessage(IntToStr(result));
end;

and got a message with the text 35531136.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384