Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

best practice: error handling

31 views
Skip to first unread message

milouz

unread,
May 31, 2011, 10:01:19 AM5/31/11
to
Hi,

I'm wondering about the best way to handle errors in Ada when calling
a function/procedure that may fails.

For example, when programming on Unix systems in C, the called
functions usually return a zero or positive value on success and -1 on
failure, while setting a global variable (errno) to give some
informations about the failure.

What's the best way to deal with that kind of error in Ada ?
Throwing an exception ? Returning some error code ? Passing a special
parameter ?...

Vinzent Hoefler

unread,
May 31, 2011, 1:13:16 PM5/31/11
to
milouz wrote:

> I'm wondering about the best way to handle errors in Ada when calling
> a function/procedure that may fails.

Well, I'd say, there is no "best way". It depends on the application and
the precise context.

> For example, when programming on Unix systems in C, the called
> functions usually return a zero or positive value on success and -1 on
> failure, while setting a global variable (errno) to give some
> informations about the failure.

As rule of thumb I'd suggest the following:

If the error should not occur under (almost) all sane circumstances (for
instance, waiting on a semaphore should always succeed, unless the handle
is invalid), then raise an exception.

If you kind of expect the call to fail under normal conditions, a proper
out-Parameter (like "Success" ;) can be added.

Keep in mind, that returning some error requires a function, and until now
functions can't have "out"-parameters, so returning an "error code" is only
possible for those subroutines which do not need to have "out"-parameters.
So for the sake of having a consistent interface, you might want to to use an
"out"-parameter, regardless if the subroutine in question could otherwise be
expressed as function or not.

On the other hand, if you want a function to return its result, because it
looks more natural, you cannot use an "Success : out Boolean", so it may be
more convenient to raise an exception for errors instead of using an "out"-
parameter for the result.


Vinzent.

--
f u cn rd ths, u cn gt a gd jb n cmptr prgrmmng.

AdaMagica

unread,
May 31, 2011, 1:57:58 PM5/31/11
to
On 31 Mai, 19:13, "Vinzent Hoefler"

<0439279208b62c95f1880bf0f8776...@t-domaingrabbing.de> wrote:
> On the other hand, if you want a function to return its result, because it
> looks more natural, you cannot use an "Success : out Boolean", so it may be
> more convenient to raise an exception for errors instead of using an "out"-
> parameter for the result.

Ada 2012 will add [in]out-parameters to functions.

Hope the amendment will soon be finished. Actually this was due in
April, as far as I know. But polishing the text and correcting any
errors introduced is an overwhelming job.

Jeffrey Carter

unread,
May 31, 2011, 2:08:38 PM5/31/11
to
On 05/31/2011 07:01 AM, milouz wrote:
>
> I'm wondering about the best way to handle errors in Ada when calling
> a function/procedure that may fails.
>
> For example, when programming on Unix systems in C, the called
> functions usually return a zero or positive value on success and -1 on
> failure, while setting a global variable (errno) to give some
> informations about the failure.

When programming in C, returned error codes are often ignored, leading to errors
and security vulnerabilities. Global variables are never a good idea, especially
in a concurrent language. This approach should almost never be used.

If the "error" is exceptional, then an exception should be raised.

If the "error" is unexceptional, then a signal that cannot be ignored/misused
should be used.

For an example of the latter, consider an Index function that returns the index
of a substring ("pattern") in a larger string ("source"). It will be fairly
common for the pattern not to occur in the source, so raising an exception is
not right here.

The Index functions in Ada.Strings.Fixed return Natural, with a value of zero
returned if the pattern is not found in the source. However, this can be
ignored/misused; calling code can omit the check for zero and use the returned
value as if it were a valid index into the string. Sometimes this will cause an
exception, but there are cases where it will not; such cases can be hard to find.

The correct way to handle this is for the function to return a variant record:

type Index_Result (Found : Boolean := False) is record
case Found is
when False =>
null;
when True =>
Index : Positive;
end case;
end record;

function Index (Source : in String; Pattern : in String) return Index_Result;

Now the signal (the discriminant) cannot be ignored, and the Index Component
cannot be misused.

--
Jeff Carter
"Clear? Why, a 4-yr-old child could understand this
report. Run out and find me a 4-yr-old child. I can't
make head or tail out of it."
Duck Soup
94

Dmitry A. Kazakov

unread,
May 31, 2011, 4:02:42 PM5/31/11
to
On Tue, 31 May 2011 11:08:38 -0700, Jeffrey Carter wrote:

> On 05/31/2011 07:01 AM, milouz wrote:
>>
>> I'm wondering about the best way to handle errors in Ada when calling
>> a function/procedure that may fails.
>>
>> For example, when programming on Unix systems in C, the called
>> functions usually return a zero or positive value on success and -1 on
>> failure, while setting a global variable (errno) to give some
>> informations about the failure.
>
> When programming in C, returned error codes are often ignored, leading to errors
> and security vulnerabilities. Global variables are never a good idea, especially
> in a concurrent language. This approach should almost never be used.
>
> If the "error" is exceptional, then an exception should be raised.
>
> If the "error" is unexceptional, then a signal that cannot be ignored/misused
> should be used.

Exception *is* an signal, but the point correct.

> For an example of the latter, consider an Index function that returns the index
> of a substring ("pattern") in a larger string ("source"). It will be fairly
> common for the pattern not to occur in the source, so raising an exception is
> not right here.

This looks like a premature optimization, but OK.

> The Index functions in Ada.Strings.Fixed return Natural, with a value of zero
> returned if the pattern is not found in the source. However, this can be
> ignored/misused; calling code can omit the check for zero and use the returned
> value as if it were a valid index into the string. Sometimes this will cause an
> exception, but there are cases where it will not; such cases can be hard to find.
>
> The correct way to handle this is for the function to return a variant record:
>
> type Index_Result (Found : Boolean := False) is record
> case Found is
> when False =>
> null;
> when True =>
> Index : Positive;
> end case;
> end record;
>
> function Index (Source : in String; Pattern : in String) return Index_Result;
>
> Now the signal (the discriminant) cannot be ignored, and the Index Component
> cannot be misused.

Again, the point is valid, but the example is not good. Zero index is as
good as Index_Result. In both cases an attempt to use the index will cause
an exception. So both return an "invalid index."

Both solutions are equivalent and both are probably worse than exception
propagation (ignoring performance issues). The problem with them is same as
with IEEE floats. The "error" detection is postponed until index is
actually used, while good design would be earliest possible detection. Of
course much depends on the usage, i.e. where the result is supposed to be
used.

Actually the language lacks a construct for test-then-use. E.g. a short-cut
for nasty and inefficient:

if Ptr /= null then
declare
Object : T renames Ptr.all;
begin
... use object

if Key in Map then
declare
Element : T renames Map (Key);
begin
... use element

if X in S'Class then
declare
Y : S'Class renames S'Class (X);
begin
... use it

if X in Integer'Range then
declare
Y : Integer := Integer (X);
begin
... use it

and so on.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de

Jeffrey Carter

unread,
May 31, 2011, 4:35:22 PM5/31/11
to
On 05/31/2011 01:02 PM, Dmitry A. Kazakov wrote:
>
> Again, the point is valid, but the example is not good. Zero index is as
> good as Index_Result. In both cases an attempt to use the index will cause
> an exception. So both return an "invalid index."

Hardly. Some valid and fairly common uses include getting the part of Source
before or after Pattern, both of which can succeed with an invalid result with
an index of zero.

Dmitry A. Kazakov

unread,
May 31, 2011, 5:40:50 PM5/31/11
to
On Tue, 31 May 2011 13:35:22 -0700, Jeffrey Carter wrote:

> On 05/31/2011 01:02 PM, Dmitry A. Kazakov wrote:
>>
>> Again, the point is valid, but the example is not good. Zero index is as
>> good as Index_Result. In both cases an attempt to use the index will cause
>> an exception. So both return an "invalid index."
>
> Hardly. Some valid and fairly common uses include getting the part of Source
> before or after Pattern, both of which can succeed with an invalid result with
> an index of zero.

A good point. Agreed.

Yannick Duchêne (Hibou57)

unread,
Jun 11, 2011, 9:31:39 AM6/11/11
to
Le Tue, 31 May 2011 16:01:19 +0200, milouz <a.mich...@gmail.com> a
écrit:

I would say : at least the “errno” strategy, is not concurrency friendly.
if two threads of a same process commit errors, then you are in trouble
(one may overwrite the “errno” value of the other). Exceptions would be
cleaner for such a case (providing your “errno” is not owned by threads).

Also depends on whether or not the errors you have in mind are more or
less predictable. As two examples : a file write error is unpredictable, a
user input error at the command line or at a use prompt, is on the
contrary, predictable and should be supposed to be likely to happen. For
the former, an exception would be the best in my opinion, and for the
latter, either an exception or a result property could be OK; while I
would personally be on favor of a result property, because a user error is
potentially a normal result of a user input.


--
“Syntactic sugar causes cancer of the semi-colons.” [Epigrams on
Programming — Alan J. — P. Yale University]
“Structured Programming supports the law of the excluded muddle.” [Idem]
“c++; /* this makes c bigger but returns the old value */” [Anonymous]

Yannick Duchêne (Hibou57)

unread,
Jun 11, 2011, 9:38:10 AM6/11/11
to
Le Tue, 31 May 2011 19:13:16 +0200, Vinzent Hoefler
<0439279208b62c95...@t-domaingrabbing.de> a écrit:

> If you kind of expect the call to fail under normal conditions, a proper
> out-Parameter (like "Success" ;) can be added.
>
> Keep in mind, that returning some error requires a function, and until
> now
> functions can't have "out"-parameters, so returning an "error code" is
> only
> possible for those subroutines which do not need to have
> "out"-parameters.
I strongly suspect this is precisely the reason why Ada 2012 will add out
parameters to function ;)
First, I though this was a silly idea, and later, from time to time, I
though this was indeed a good idea, at least to return status from
function, without the need to define wrapper types every where (at least,
while Ada do not have provision to return proper tuples like functional
languages do). Moreover, a function may still be conceptually Pure, even
with out parameters ;)

Yannick Duchêne (Hibou57)

unread,
Jun 11, 2011, 9:48:38 AM6/11/11
to
Le Tue, 31 May 2011 20:08:38 +0200, Jeffrey Carter
<spam.jrc...@spam.not.acm.org> a écrit:

> The correct way to handle this is for the function to return a variant
> record:
>
> type Index_Result (Found : Boolean := False) is record
> case Found is
> when False =>
> null;
> when True =>
> Index : Positive;
> end case;
> end record;

That's an idiom I also use with SML (I feel to remember there is a
dedicated name for that idiom, but I forget what it is this name), as
access to the result is automatically constrained to whether or not the
function succeed. But while this is good for request-like function (like
get the item with this or that property), this come to be less handy when
many and many functions or procedure return a common type may also return
error, because you end to as many type definition.

Another case is the if-previous-succeed-then-go-on-else-bypass idiom.
Multiple procedure get an in/out boolen parameter. If on input, the
parameter is False, then the procedure bypass its own process, if the
input parameter is True, the procedure do its job and set the parameter to
False on output if ever it fails somewhere. This is a useful idiom with
SPARK which does not allow exceptions. Combined with that strategy, a
wrapper type would lead to a mess.

Yannick Duchêne (Hibou57)

unread,
Jun 11, 2011, 9:57:23 AM6/11/11
to
Le Tue, 31 May 2011 22:02:42 +0200, Dmitry A. Kazakov
<mai...@dmitry-kazakov.de> a écrit:

> Again, the point is valid, but the example is not good. Zero index is as
> good as Index_Result. In both cases an attempt to use the index will
> cause
> an exception. So both return an "invalid index."
Disagree: an index is an index, not an error status; otherwise this is a
kind of special-value as special-meaning (a special meaning which outside
of the normal type semantic area). My experience says its dangerous,
because it is always hard to read that either -1 or 0 or who know what,
was intended to mean Error. Chance are that many people will read 0 as
meaning “Index 0”.

Dmitry A. Kazakov

unread,
Jun 11, 2011, 10:07:34 AM6/11/11
to
On Sat, 11 Jun 2011 15:57:23 +0200, Yannick Duchêne (Hibou57) wrote:

> Le Tue, 31 May 2011 22:02:42 +0200, Dmitry A. Kazakov
> <mai...@dmitry-kazakov.de> a écrit:
>> Again, the point is valid, but the example is not good. Zero index is as
>> good as Index_Result. In both cases an attempt to use the index will
>> cause an exception. So both return an "invalid index."
> Disagree: an index is an index, not an error status; otherwise this is a
> kind of special-value as special-meaning (a special meaning which outside
> of the normal type semantic area).

You are arguing against special value for an exception. Both Zero index and
Index_Result constrained to Found = False are special values.

> My experience says its dangerous,
> because it is always hard to read that either -1 or 0 or who know what,
> was intended to mean Error. Chance are that many people will read 0 as
> meaning “Index 0”.

No, the value should be invalid for indexing and sustain other operations.
Compare it with IEEE's NaN. Zero index could have such property if we
redefined index arithmetic so that x + 0 = 0. It is possible to do
distinguishing index and offset types in arithmetic (compare with Time and
Duration), but to much headache to bother. Index_Result is simpler and
works out of the box.

BTW, I prefer exceptions to any kind of special values. But as Jeff Carter
said, there can be gray zones where special values meaning "not so
exceptional" can be used.

Simon Wright

unread,
Jun 11, 2011, 12:12:23 PM6/11/11
to
"Yannick Duchêne (Hibou57)" <yannick...@yahoo.fr> writes:

> I would say : at least the “errno” strategy, is not concurrency
> friendly. if two threads of a same process commit errors, then you are
> in trouble (one may overwrite the “errno” value of the
> other). Exceptions would be cleaner for such a case (providing your
> “errno” is not owned by threads).

In VxWorks, errno is task-specific. (with GNAT, all Ada tasks are
VxWorks tasks, but there are VxWorks tasks which aren't Ada tasks).

Dmitry A. Kazakov

unread,
Jun 11, 2011, 12:26:47 PM6/11/11
to
On Sat, 11 Jun 2011 15:31:39 +0200, Yannick Duchêne (Hibou57) wrote:

> I would say : at least the “errno” strategy, is not concurrency friendly.

But errno (and Windows' GetLastError) is not a return code. The return code
is a local object which effect is always limited to the current task, as
well as the exception (when it starts propagating). errno is a global scope
function / object, syntactically unrelated to the original call. errno is
bad even without any concurrency.

Randy Brukardt

unread,
Jun 12, 2011, 1:19:58 AM6/12/11
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:1835s5a847r9q$.191jzvxwbypk8.dlg@40tude.net...
...

> BTW, I prefer exceptions to any kind of special values. But as Jeff Carter
> said, there can be gray zones where special values meaning "not so
> exceptional" can be used.

I generally agree. But there are sometimes cases to the contrary. With Claw,
we originally raised an exception when the Find routine did not find the
menu entry, window, or whatever it was looking for. But this turned out to
be a major pain in practice. Moreover, you can make an argument that
"Not_Found" is not an error per-se; it is a normal result. As such, we
changed those routines to return a special value (which does not work in
subsequent operations). It's still possible to mistakenly use that value as
if it was a valid object, but you'll always get an exception if you do so.

Which just shows that there is no one-size-fits-all solution to this
problem. There is no substitute for thinking about the problem you are
solving and how the function is going to be used.

Randy.


0 new messages