What do you consider "best practice" when it comes to error handling errors in a consistent way in a C library.
There are two ways I've been thinking of:
Always return error code. A typical function would look like this:
MYAPI_ERROR getObjectSize(MYAPIHandle h, int* returnedSize);
The always provide an error pointer approach:
int getObjectSize(MYAPIHandle h, MYAPI_ERROR* returnedError);
When using the first approach it's possible to write code like this where the error handling check is directly placed on the function call:
int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
// Error handling
}
Which looks better than the error handling code here.
MYAPIError error;
int size;
size = getObjectSize(h, &error);
if(error != MYAPI_SUCCESS) {
// Error handling
}
However, I think using the return value for returning data makes the code more readable, It's obvious that something was written to the size variable in the second example.
Do you have any ideas on why I should prefer any of those approaches or perhaps mix them or use something else? I'm not a fan of global error states since it tends to make multi threaded use of the library way more painful.
EDIT: C++ specific ideas on this would also be interesting to hear about as long as they are not involving exceptions since it's not an option for me at the moment...
Returning error code is the usual approach for error handling in C.
But recently we experimented with the outgoing error pointer approach as well.
It has some advantages over the return value approach:
You can use the return value for more meaningful purposes.
Having to write out that error parameter reminds you to handle the error or propagate it. (You never forget checking the return value of
fclose
, don't you?)If you use an error pointer, you can pass it down as you call functions. If any of the functions set it, the value won't get lost.
By setting a data breakpoint on the error variable, you can catch where does the error occurred first. By setting a conditional breakpoint you can catch specific errors too.
It makes it easier to automatize the check whether you handle all errors. The code convention may force you to call your error pointer as
err
and it must be the last argument. So the script can match the stringerr);
then check if it's followed byif (*err
. Actually in practice we made a macro calledCER
(check err return) andCEG
(check err goto). So you don't need to type it out always when we just want to return on error, and can reduce the visual clutter.Not all functions in our code has this outgoing parameter though. This outgoing parameter thing are used for cases where you would normally throw an exception.
I ran into this Q&A a number of times, and wanted to contribute a more comprehensive answer. I think the best way to think about this is how to return errors to caller, and what you return.
How
There are 3 ways to return information from a function:
Return Value
You can only return value is a single object, however, it can be an arbitrary complex. Here is an example of an error returning function:
One benefit of return values is that it allows chaining of calls for less intrusive error handling:
This not just about readability, but may also allow processing an array of such function pointers in a uniform way.
Out Argument(s)
You can return more via more than one object via arguments, but best practice does suggest to keep the total number of arguments low (say, <=4):
Out of Band
With setjmp() you define a place and how you want to handle an int value, and you transfer control to that location via a longjmp(). See Practical usage of setjmp and longjmp in C.
What
Indicator
An error indicator only tells you that there is a problem but nothing about the nature of said problem:
This is the least powerful way for a function to communicate error state, however, perfect if caller cannot respond to the error in a graduated manner anyways.
Code
An error code tells the caller about the nature of the problem, and may allow for a suitable response (from the above). It can be return value, or like the look_ma() example above an error argument.
Object
With an error object, the caller can be informed about arbitrary complicated issues. For example, an error code and a suitable human readable message. It can also inform the caller that multiple things went wrong, or an error per item when processing a collection:
Instead of pre-allocating the error array, you can also (re)allocate it dynamically as needed of course.
Callback
Callback is the most powerful way to handle errors, as you can tell the function what behavior you would like to see happen when something goes wrong. A callback argument can be added to each function, or if customization uis only required per instance of a struct like this:
One interesting benefit of a callback is that it can be invoked multiple times, or none at all in the absence of errors in which there is no overhead on the happy path.
There is, however, an inversion of control. The calling code does not know if the callback was invoked. As such, it may make sense to also use an indicator.
What you could do instead of returning your error, and thus forbidding you from returning data with your function, is using a wrapper for your return type:
Then, in the called function:
Please note that with the following method, the wrapper will have the size of MyType plus one byte (on most compilers), which is quite profitable; and you won't have to push another argument on the stack when you call your function (
returnedSize
orreturnedError
in both of the methods you presented).EDIT:If you need access only to the last error, and you don't work in multithreaded environment.
You can return only true/false (or some kind of #define if you work in C and don't support bool variables), and have a global Error buffer that will hold the last error:
I was pondering this issue recently as well, and wrote up some macros for C that simulate try-catch-finally semantics using purely local return values. Hope you find it useful.
First approach is better IMHO: