Keith said:
Well, you *could* return as many unique error conditions as you like
with a simple char* return value:
const char *const gl_EOF = "getline: EOF";
const char *const gl_io_error = "getline: I/O error";
const char *const gl_malloc_failed = "getline: malloc failed";
...
Given the availability of feof() and ferror(), and the
sub-rosa knowledge that the only other possible failure mode
is NULL from realloc(), such a dodge didn't seem necessary.
Purists might argue (with some justification) that the sub-rosa
knowledge is a Bad Thing; my feeling was that enumerating all
three "exceptional" possibilities in the documentation (thus
making them part of the "interface contract") was good enough.
If I'd felt it desirable to return more information about
the failure modes, I'd probably have abandoned the approach of
overloading both success and all those failure modes onto a
single returned value. Instead, I'd have returned the "payload"
in one place and a "status" in another -- almost all (Open)VMS
facilities worked this way, and reasonably well. This approach
would also have allowed me to return the partial line payload
that preceded an error, instead of just discarding what had
been read, and that would have been a Good Thing. (Principle:
low-level routines shouldn't make higher-level decisions.) But
it didn't "feel" worth while to clutter the interface to preserve
information I really couldn't see myself making use of.
Different people design different interfaces for the same
task! Different people decompose the same task in different ways!
Ultimately, it comes down to what might be called "taste" (there's
just no point in arguing with Gus), or to put it on a more respectable
footing it comes down to a guess about the likely usage scenarios for
the new facility. There might (or might not) be the germ of a thesis
topic for someone who wants to make a study of how different programmers
approach similar problems: were you corrupted by an early exposure to
Forth, was your mother frightened by a COBOL compiler?
[...]
Another approach is to implement something resembling errno, but that
can make it difficult to tie an error to a specific call.
Usually, when I want to preserve more information than I "tasted"
was appropriate for getline(), I'll have the function return a status
code and pass the payload through an additional argument:
#define GETLINE_OK 0
#define GETLINE_EOF (-1)
#define GETLINE_ERR (-2)
#define GETLINE_NOMEM (-3)
int getline(FILE *stream, char **bufptr);
.... and the caller would write
char *line;
int status;
while ((status = getline(stream, &line)) == GETLINE_OK)
Sometimes it's convenient to make the "status" value be NULL
for success or else a `const char*' pointing to an error message:
char *status;
if ((status = somefunc(args)) != NULL) {
fprintf (stderr, "somefunc: %s\n", status);
exit (EXIT_FAILURE);
}
"There are nine and sixty ways of constructing tribal lays,
And every single one of them is right!" -- Rudyard Kipling