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:
- call
fn()
- 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.