[comp.lang.c] Functions returning Error codes or actual info

vrm@cathedral.cerc.wvu.wvnet.edu (Vasile R. Montan) (09/10/90)

   I am making a set of functions which return different types of
values: strings, integers, doubles, etc.  For example:
          char *get_string();
However, I would also like the function to return an error code if the
function fails.  I cannot just return a NULL pointer because I want
the function to be the same as all of the other get_xxx's. I have seen
this done several different ways in C and am wondering if there is an
accepted "proper" way of doing it.

Most often in C, I see the error code being returned so it can be used
inside a control statement.  This forces the actual information to be
returned in an "out" mode parameter:

CASE 1:	if (error_code = get_string(parm, &info)) {...}

I have also seen functions which set a global variable to indicate
that an error has occurred:

CASE 2:	info = get_string(parm);
	if (error_code) {...}

I have not seen these used often, but they are also valid options:

CASE 3:	info = get_string (parm, &error_code);
	if (error_code) {...}

CASE 4:	get_string (parm, &info, &error_code);
	if (error_code) {...}

   BTW, I have some other functions which do not return any
information, so they always return an error code.  Does this mean I
should use CASE 1 just to keep them consistent?

   So, which do you feel is the best way to implement these functions?
This has probably been discussed here before, but I missed it.  If it
has and someone has saved the discussion, I would like to see it.  If
it has not, I would like to hear everyone's opinion.  I will summarize
any responses I receive.

-- Vasile

ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (09/11/90)

In article <772@babcock.cerc.wvu.wvnet.edu>,
vrm@cathedral.cerc.wvu.wvnet.edu (Vasile R. Montan) asks
about returning error information.

Had you considered doing it with functions?

Example:  UNIX System V <math.h> library; there is a function
matherr() which each of the math functions calls when it detects
an error.  matherr() is given a description of the error and can
return a corrected value or do whatever you like.  If you don't
define your own matherr(), the loader picks up a default version
from "libm.a".

Example:  the HP-UX extension to UNIX system calls; you can
register an error handling function, and whenever a system call
is about to set errno and return an error value it will call
your function instead, passing it the value it was going to
assign to errno and a <varargs> pointer to the argument list of
the system call, and the system call number.  Your function can
do anything you like, including correcting the arguments and
retrying.  (I implemented this for SunOS 3.2; the basic trick
was that all the system calls branch to 'cerror:' to report
errors, and all that had to be done was to plug in a different
cerror.)

Example:  the way Algol 68 handled transput errors.  Files in
Algol 68 were implemented as records some of whose fields were
functions for handling error or other conditions.  You could
assign your own functions to these fields.  An error having been
detected, the function would be called with the file and any
other information available about the error as parameters.

Functions are _much_ more flexible than returning error codes.
-- 
Heuer's Law:  Any feature is a bug unless it can be turned off.

brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (09/11/90)

In article <3723@goanna.cs.rmit.oz.au> ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) writes:
> Functions are _much_ more flexible than returning error codes.

Well, not really; exception handlers can always be implemented with
straight error codes and some ugly syntax. It's just more efficient, and
easier for both the compiler and the programmer to deal with, when the
language supports exceptions as a program construct. (That means more
than Ada's wimpy one-handler-per-function stuff.)

---Dan
``Ada isn't a religion. Ada is a disease.'' HJB

flaps@dgp.toronto.edu (Alan J Rosenthal) (09/11/90)

vrm@cathedral.cerc.wvu.wvnet.edu (Vasile R. Montan) writes:
>   I am making a set of functions which return different types of
>values: strings, integers, doubles, etc.  For example:
>          char *get_string();
>However, I would also like the function to return an error code if the
>function fails.  I cannot just return a NULL pointer because I want
>the function to be the same as all of the other get_xxx's.

A method you didn't mention is to return a null pointer, and have a #defined
constant for this.  If you return some kind of distinguished value already for
the integer functions, such as -1, you may want to have:

	#define INTERROR (-1)

in your .h file.  So put:

	#define STRINGERROR ((char *)0)

in your .h file as well.

vrm@babcock.cerc.wvu.wvnet.edu (Vasile R. Montan) (09/12/90)

   As promised, here are the email responses I received on my question
on whether functions should return actual data or error codes.  It
seems the general consensus is to return the error code as the value
of the function and return the actual value in an out-mode parameter.

   Thanks to everyone who responded.

-- Vasile
======================================================================
From robert@cs.arizona.edu:
Case 1, function value indicates success/failure, usually works well
with the enclosing control structure.  You won't have to store something
into a separate variable and gives a good "functional" approach to your
solution:

    while (get_x(&info))
      deal_with(info);

or

    switch (get_x(&info)) {
      case E1 :
      case E2 :
      case E3 :
    }

======================================================================
From dinl!noren@ncar.ucar.edu:
Your raised a real good question, and I'd like to see the responses
you get.  I waiver all over the place because I don't have a
satisfactory solution myself.

What I use most often is have the function return the value I want,
using a clearly illeagal to flag an error (e.g., return a NULL on
a function that returns a string).  Prior to the return with the
error-value I set the global errno variable to some meaningful value
(or have my own extensions to it with a wrapper around perror(3) to
display my extension text).  In this way I am using (what I interpret,
there is such a thing) the UNIX (or is it the C library) philosophy of 
function error reporting and even have the library functions do much
of the work in setting errno for me.  For instance, if my socket
handler fails, its usually because a library call to the O.S. or
C library failed, and that function returns an errno indicating why,
which I leave untouched as I return out of my function with an error
value.

This approach doesn't work all the time, but oh well.

Thanks for posing the question.  I hope there is a lot of interesting
discussion.

======================================================================
From ari@eleazar.dartmouth.edu:
In article <772@babcock.cerc.wvu.wvnet.edu> you write:
>
>   I am making a set of functions which return different types of
>values: strings, integers, doubles, etc.  For example:
>          char *get_string();
>However, I would also like the function to return an error code if the
>function fails.  I cannot just return a NULL pointer because I want
>the function to be the same as all of the other get_xxx's. I have seen
>this done several different ways in C and am wondering if there is an
>accepted "proper" way of doing it.

I find the easiest to code, most consistent, and most robust approach
is to return the error code as the function's result. As in,
	Error foo(params..., result *output)
I then use statements like:
	err = foo(param, &output);
	if (err)
		return(err); /* propagate error until someone can handle it */
For a while, I tried jumping to a label when an error was detected,
especially if there was a lot of cleaning up to do. For instance,
	char *newptr = NULL;
	char *otherptr = NULL;

	newptr = (char *) malloc(100);
	if (! newptr) {
		err = ERR_MEMORY;
		goto error;
	}
	otherptr = (char *) malloc(200);
	if (! otherptr) {
		err = ERR_MEMORY;
		goto error;
	}
	err = do_something();
	if (err)
		goto error;
	return(0); /* zero is no error */
    error:
	if (newptr) free(newptr);
	if (otherptr) free(otherptr);
	return(err);
However, the above scheme led to confusing code (of course
it did, it has goto's!). I now use a combination of 
return statements and nested if's. For instance, the above
chunk of code would be written:
	char *newptr, *otherptr;
	
	newptr = malloc(100);
	if (newptr) {
		otherptr = malloc(200);
		if (otherptr) {
			err = do_something();
			if (! err)
				return(0);
			/* we're now falling through to
			   all of the cleanup code */
			free(otherptr);
		}
		free(newptr);
	}
	if (! err) err = ERR_MEMORY;
	return(err);

Or something like that. Anyway, making everything nest and then
falling through to cleanup code seems to be reliable. Of course,
this isn't really about error codes, but I thought it would
be interesting.	-- 
======================================================================
From gary@avo.hp.com Tue Sep 11 02:45:35 1990
> Most often in C, I see the error code being returned so it can be used
> inside a control statement.  This forces the actual information to be
> returned in an "out" mode parameter:

> CASE 1:	if (error_code = get_string(parm, &info)) {...}

In the project I've just started, we have agreed on this standard for
consistency.  There is one exception: if you have a function that would
work nicely as a predicate in an "if" statement AND if the error  case
can be handled reasonably in the return parameter, then you can return 
the predicate value.  An example might help.  Let's say you maintain
some list of objects.  And you want a function IsEmpty(list).  Clearly
you want  to be able to write:
    if (IsEmpty(List1))...
Further, the only likely error case is List1 being a NULL instead
of your list-head; and clearly in this case you can return TRUE.
Another example: You want NumInList(list).  Now, you can use a
negative return value to indicate an error.

> I have also seen functions which set a global variable to indicate
> that an error has occurred:

> CASE 2:	info = get_string(parm);
> 	if (error_code) {...}

I hate this.  Using a global like this has lots of problems.
If you make another call, you could blot out a useful error code.  
(This is always a problem with errno in UNIX -- you have to save it before
any other I/O clears it.)

> CASE 3:	info = get_string (parm, &error_code);
> 	if (error_code) {...}

I think this is pretty common.  You write your functions as if there
were no error handling then you append a final parameter for error
control.  There is nothing wrong with this method.

>    BTW, I have some other functions which do not return any
> information, so they always return an error code.  Does this mean I
> should use CASE 1 just to keep them consistent?
It certainly is one of the advantages that CASE 1 has.

======================================================================
From taumet!taumet!steve@uunet.UU.NET Tue Sep 11 11:51:31 1990

My suggestion is to decide on the abstraction you want for the functions,
then write them that way.

I would NEVER use a global variable for passing error information.
This technique (errno) is used in the standard C library.  The ANSI
committee recognized this was a horrible idea, but it was existing
practice, and could not reasonably be changed.  One problem comes
when a function which might set the variable calls another which
also might set the variable -- the interactions get very hard to
manage.  Another problem comes in multi-threaded programs or
programs with interrupts.  You might see the variable set and assume
it was your error, when it was an error from some other task.

Using an "out" variable for data and returning a status code has some
advantages, it seems to me, since you can then write
	if( get_data(&info) ) {
		... ok ...
	}
	else {
		... fail ...
	}
or
	switch( get_data(&info) ) {
	    case OK:
		...
	    case EOF:
		...
	    case ERROR:
		...
	    etc ...
	}

mhill@fiveliter.EBay.Sun.COM (Maurice Hill) (09/12/90)

flaps@dgp.toronto.edu (Alan J Rosenthal) writes:

>vrm@cathedral.cerc.wvu.wvnet.edu (Vasile R. Montan) writes:
>>   I am making a set of functions which return different types of
>>values: strings, integers, doubles, etc.  For example:
>>          char *get_string();
>>However, I would also like the function to return an error code if the
>>function fails.  I cannot just return a NULL pointer because I want
>>the function to be the same as all of the other get_xxx's.

Perhaps you could implement a different error strategy.  Instead of
returning an error code, set a global such as is done with errno.  

Errno is an index into an array of predefined error messages.  You can
access the list yourself or use the function perror("msg") to print out
your msg plus the error message that corresponds to errno.

mhill@fiveliter.EBay.Sun.COM (Maurice Hill) (09/12/90)

flaps@dgp.toronto.edu (Alan J Rosenthal) writes:

>vrm@cathedral.cerc.wvu.wvnet.edu (Vasile R. Montan) writes:
>>   I am making a set of functions which return different types of
>>values: strings, integers, doublec, etc.  For example:
>>          char *get_string();
>>However, I would also like the function to return an error code if the
>>function fails.  I cannot just return a NULL pointer because I want
>>the function to be the same as all of the other get_xxx's.

Perhaps you could implement a different error strategy.  Instead of
returning an error code, set a global such as is done with errno.  

Errno is an index into an array of predefined error messages.  You can
access the list yourself or uce the function `error("msg") to print out
your msg plus the error message that corresponds to errno.

flaps@dgp.toronto.edu (Alan J Rosenthal) (09/12/90)

mhill@fiveliter.EBay.Sun.COM (Maurice Hill) writes:

>flaps@dgp.toronto.edu (Alan J Rosenthal) writes:

No, I didn't write any of the text which appears in that posting.

mhill@fiveliter.EBay.Sun.COM (Maurice Hill) (09/12/90)

flaps@dgp.toronto.edu (Alan J Rosenthal) writes:

>mhill@fiveliter.EBay.Sun.COM (Maurice Hill) writes:

>>flaps@dgp.toronto.edu (Alan J Rosenthal) writes:

>No, I didn't write any of the text which appears in that posting.


Sorry about that. 

campbell@redsox.bsw.com (Larry Campbell) (09/13/90)

Magic Values are a Bad Thing.  You have to remember to check the returned
value to see if it's a Magic Value, and if you forget to check (a very
common mistake), your code can break in mysterious ways.  For example, who
*really* bothers to check *every* call to malloc to see if the returned
value is NULL?  Magic Values also mean you often have to pervert the type
system -- like making fgetc() return an int instead of a char (what
beginning C programmer has not made the mistake of declaring its result as a
char?)

A much better approach is to use exceptions, a la Modula-3.  Functions are
assumed to *always* return the asked-for value.  If an error occurs, an
exception is raised; *if* you're prepared to check for the error, you
declare an exception handler which gets control; otherwise, the default
exception handler takes over, which usually just tells you the name of the
exception, and where it occurred, and dumps core.

I have implemented a portable exception handling facility for C that we are
using in all our projects here and have found to be very useful.  Using its
syntax, here is an example:

    1	    TRY
    2	        FOO x;
    3	        x = get_next_foo();
    4	        fondle_foo(x);
    5	    EXCEPT
    6	        CASE(Exc_No_More_Foos)
    7		    printf("all foos fondled!\n");
    8		    return;
    9		CASE(Exc_Foo_Quota_Exceeded)
   10		    printf("buy more foos!\n");
   11		    exit(1);
   12	    ENDTRY

get_next_foo() is defined *always* to return a FOO, or to raise an
exception.  If get_next_foo() runs out of FOOs, for example, it just says:

    RAISE(Exc_No_More_Foos)

This will cause control to pass immediately to line 7 above.  This RAISE
statement could occur inside get_next_foo(), or inside any routine called by
get_next_foo().

If an exception is raised that is not listed in the EXCEPT clause (in the
present example, any exception other than Exc_No_More_Foos or
Exc_Foo_Quota_Exceeded), the stack is unwound until a TRY-EXCEPT clause is
found that wants to catch the exception.  If no willing handler is found, the
default exception handler is invoked (which in our implementation prints the
name of the exception and the source file name and line number where it was
raised, and then dumps core.)

Using this facility, it is "safe" not to bother to check for the success or
failure of a function call; by "safe" I mean that the behavior of the
program is well-defined -- it crashes immediately with a message that tells
you exactly what happened.  If your functions return Magic Values and you
forget, or just don't bother, to check for them, then when they fail, your
program's behavior becomes undefined.
-- 
Larry Campbell                          The Boston Software Works, Inc.
campbell@redsox.bsw.com                 120 Fulton Street
wjh12!redsox!campbell                   Boston, MA 02109

swh@hpcupt1.HP.COM (Steve Harrold) (09/13/90)

>>> I have implemented a portable exception handling facility for C that we are
>>> using in all our projects here and have found to be very useful.  
----

This sounds like exciting (and useful) stuff!!  Where can one obtain a copy?

campbell@redsox.bsw.com (Larry Campbell) (09/14/90)

A number of readers have written asking if the exception handling facility I
described earlier in this thread is publicly available.  It soon will be.
Although I designed it nearly two years ago, I didn't get around to
implementing it until last month.  Our experience with it has been quite
good, but has pointed out a few rough spots that I'd like to iron out before
publishing it; there are also some more or less gratuitous dependencies on
other aspects of our coding environment that most people probably aren't
interested in.  Once the rough edges are filed off and the uninteresting BSW
dependencies are removed, I'll be posting it to alt.sources and
comp.sources.misc.  It should work with any C compiler that supports
setjmp/longjmp; we've used it on VAX/VMS, HP 3000, Wang VS, and PC-DOS with
Turbo C.

In the meantime, an approach very similar to ours is described in a research
report available from the DEC Systems Research Center:

*************** SRC Research Report #40
Date: March 21, 1989

    "Implementing Exceptions in C."
    Eric S. Roberts.
    13 pages.

Author's Abstract

    Traditionally, C programmers have used specially designated return
    codes to indicate exception conditions arising during program
    execution. More modern languages offer alternative mechanisms
    that integrate exception handling into the control structure.
    This approach has several advantages over the use of return codes:
    it increases the likelihood that programming errors will be
    detected, makes it easier to structure the specification of an
    abstraction, and improves the readability of the implementation
    by providing better syntactic separation between handling of
    conventional and exceptional cases. This paper describes a set
    of language extensions to support exception handling in C, and
    a preprocessor-based implementation of those extensions that
    demonstrates both the feasibility and the portability of this
    approach.

-- 
Larry Campbell                          The Boston Software Works, Inc.
campbell@redsox.bsw.com                 120 Fulton Street
wjh12!redsox!campbell                   Boston, MA 02109

friedl@mtndew.Tustin.CA.US (Steve Friedl) (09/19/90)

Larry Campbell first mentions the exception handling facility that
he has been working on, and then says:

> In the meantime, an approach very similar to ours is described in
> a research report available from the DEC Systems Research Center:
> 
> *************** SRC Research Report #40
> Date: March 21, 1989
> 
>     "Implementing Exceptions in C."
>     Eric S. Roberts.
>     13 pages.

DEC SRC will provide these reports for the asking (at least they
did two years ago when I requested and received Leslie Lamport's
excellent paper on a fast mutual exclusion algorithm)

The address I last used was:

	Report Distribution Desk
	DEC/SRC Reading Room, Mailstop UCT
	130 Lytton Avenue
	Palo Alto, CA  94301  USA

A big fat sloppy thank you to Digital for providing this very
nice service.  Thanks too to Larry for posting the abstract.

     Steve

-- 
Stephen J. Friedl, KA8CMY / I speak for me only / Tustin, CA / 3B2-kind-of-guy
+1 714 544 6561  / friedl@mtndew.Tustin.CA.US  / {uunet,attmail}!mtndew!friedl

Jesse Helms for U.S. Supreme Court Justice