4

I'm writing a CSV parsing library in C and am considering whether to express error codes as return values or as parameters passed by reference.

For example, here are the signatures for the function that counts the number of columns in a CSV file, using both designs:

// Method one
// Return value: number of columns
size_t countfields(
    unsigned char *data, // First/header row in the file
    size_t len, // Length of the buffer
    errno_t *err // Error code (0 == success)
);

// Method two
// Return value: error code (0 == success)
errno_t countfields(
    unsigned char *data, // First/header row in the file
    size_t len, // Length of the data buffer
    size_t *numfields // Number of columns
);

Obviously, this decision only applies to languages like C which don't support exceptions so the only way to test for an error is by examining designated error-holding variables after a function call.

Which method should I use: returning the error code, or using a parameter, and why?

1
  • 4
    There's also the possibility of combining return value & error code, perhaps by using negative numbers for error codes
    – Bwmat
    Jul 2, 2018 at 3:55

3 Answers 3

8

In "Writing Solid Code", Steve Maguire made a strong argument for method 1 (returning error codes through a separate parameter): for the caller, it is way harder to overlook there is an error code to be handled, at least accidentally.

The second variant makes it easy to call the function

   countfields(&data, len, &numfields);

instead of

   errno_t err = countfields(&data, len, &numfields);

and overlook the fact it does not just return numfields, but also an error code as return value.

In the first variant, one is forced to introduce a return variable for the error code, so it is harder to miss it. Of course, if this variable then gets ignored willingly is something the signature cannot prevent.

IMHO this is old wisdom which has not really changed since that old days (except the fact more people prefer using languages with exceptions).

Side note: the book title does not relate to the SOLID acronym, as it was introduced by Bob Martin 20 years ago.

4
  • 3
    It depends a lot on the language as well. What you've said definitely makes sense with regard to C, but if you were using Rust, for example, it would be extremely unidiomatic to not return a Result<T, E>, and the language itself has features that prevent you from ignoring its value. Jul 2, 2018 at 16:46
  • 1
    There is a reason C++ added [[nodiscard]], which completely changes the picture here. Jun 22, 2021 at 16:07
  • @Deduplicator: sorry, but I cannot follow you. What has C++ to do with this question about languages with no exception handling?
    – Doc Brown
    Jun 22, 2021 at 19:32
  • 1
    @DocBrown C++ also supporting exceptions has nothing to do with the point, namely that there is a reason one would sometimes ask the compiler to enforce use of the return-value. I have the idea it is more often used for functions which cannot throw anyway, not that it matters. Jun 22, 2021 at 20:28
4

Doc Brown's answer is in general correct.

However, in some cases, you want the error handling to be more explicit and more detailed (than a single integer error code, such as errno - which today is a macro looking like a variable, not a global variable anymore). You could (in many cases) choose to print some error message to stderr, but sometimes (think of a web or other kind of long-running server, or of a GUI application whose stderr is practically never read, or even lost after a call to daemon(3)), you may want to give error details elsewhere (perhaps because you want to log them to some file, or use syslog, or show them thru the GUI).

For example, both Jansson (a JSON library) and Glib (an infrastructure library for GTK & Gnome GUI) reports their error otherwise (than giving only one integral error code).

Some libraries (e.g. Ian Taylor's libbacktrace) gives the ability to register some error handling callback function (which they would call in error situations).

A bad example is IMHO the POSIX dynamic loading functions dlopen & dlsym. They report errors thru the non-reentant and stateful dlerror function which I find ugly.

1

Do it in a consistent way, and if your language or library has standards, follow the standard.

For example, in Objective-C the standard is no error numbers but NSError objects. Functions should either return YES/NO for success/failure or some object/nil for success failure, and have one parameter that is a pointer to an NSError*.

The function result tells you whether the call succeeded or not, and if an object is returned, you have a return value. If you don't care about what error there was, just success or failure, you pass NULL for the error pointer. If you want to know what error, you pass the address of an NSError*. If there is no error, then the called function leaves the error unchanged. You initialise your error variable to nil (the language does that automatically), then you can pass the same address to multiple functions and get the last error if there were any.

So with strict rules about the function arguments, you have significant freedom how to call the function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.