Paige Ruten
Paige Ruten

Reputation: 176655

What is the best way to return an error from a function when I'm already returning a value?

I wrote a function in C that converts a string to an integer and returns the integer. When I call the function I also want it to let me know if the string is not a valid number. In the past I returned -1 when this error occurred, because I didn't need to convert strings to negative numbers. But now I want it to convert strings to negative numbers, so what is the best way to report the error?

In case I wasn't clear about this: I don't want this function to report the error to the user, I want it to report the error to the code that called the function. ("Report" might be the wrong word to use...)

Here's the code:

s32 intval(const char *string) {
    bool negative = false;
    u32 current_char = 0;

    if (string[0] == '-') {
        negative = true;
        current_char = 1;
    }

    s32 num = 0;
    while (string[current_char]) {
        if (string[current_char] < '0' || string[current_char] > '9') {
            // Return an error here.. but how?
        }

        num *= 10;
        num += string[current_char] - '0';
        current_char++;
    }

    if (negative) {
        num = -num;
    }

    return num;
}

Upvotes: 42

Views: 61863

Answers (8)

chux
chux

Reputation: 153457

What is the best way to return an error from a function when I'm already returning a value?

Some additional thoughts to the various answers.


Return a structure

Code can return a value and an error code. A concern is the proliferation of types.

typedef struct {
  int value;
  int error;
} int_error;

int_error intval(const char *string);

...

int_error = intval(some_string);
if (int_error.error) {
  Process_Error();
}

int only_care_about_value = intval(some_string).value;
int only_care_about_error = intval(some_string).error;

Not-a-number and NULL

Use a special value when the function return type provides it.
Not-a-number's are not required by C, but ubiquitous.

#include <math.h>
#include <stddef.h>

double y = foo(x);
if (isnan(y)) {
  Process_Error();
}

void *ptr = bar(x);
if (ptr == NULL) {
  Process_Error();
}

_Generic/Function Overloading

Considering the pros & cons of error_t foo(&dest, x) vs. dest_t foo(x, &error),

With a cascaded use of _Generic or function overloading as a compiler extension, selecting on 2 or more types, it makes sense to differentiate the underlying function called to be based on the parameters of the call, not the return value. Return the common type, the error status.

Example: a function error_t narrow(destination_t *, source_t) that converted the value of one type to a narrower type, like long long to short and tested if the source value was in range of the target type.

long long ll = ...; 
int i;
char ch; 
error = narrow(&i, ll);
...
error = narrow(&ch, i);

Upvotes: 2

user3458
user3458

Reputation:

Take a look at how the standard library deals with this problem:

long  strtol(const  char  * restrict str,  char **restrict endptr, int base);

Here, after the call the endptr points at the first character that could not be parsed. If endptr == str, then no characters were converted, and this is a problem.

Upvotes: 5

Michael Burr
Michael Burr

Reputation: 340208

There are several ways. All have their pluses and minuses.

  • Have the function return an error code and pass in a pointer to a location to return the result. The nice thing about this there's no overloading of the result. The bad thing is that you can't use the real result of the function directly in an expression.

    Evan Teran suggested a variation of this that has the caller pass a pointer to a success variable (which can be optionally NULL if the caller doesn't care) and returns the actual value from the function. This has the advantage of allowing the function to be used directly in expressions when the caller is OK with a default value in an error result or knows that the function cannot fail.

  • Use a special 'sentinel' return value to indicate an error, such as a negative number (if normal return values cannot be negative) or INT_MAX or INT_MIN if good values cannot be that extreme. Sometimes to get more detailed error information a call to another function (such as GetLastError()) or a global variable needs to be consulted (such as errno). This doesn't work well when your return value has no invalid values, and is considered bad form in general by many people.

    An example function that uses this technique is getc(), which returns EOF if end of file is reached or an error is encountered.

  • Have the function never return an error indication directly, but require the caller to query another function or global. This is similar to how VB's "On Error Goto Next" mode works - and it's pretty much universally considered a bad way to go.

  • Yet another way to go is to have a 'default' value. For example, the atoi() function, which has pretty much the same functionality that your intval() function, will return 0 when it is unable to convert any characters (it's different from your function in that it consumes characters to convert until it reaches the end of string or a character that is not a digit).

    The obvious drawback here is that it can be tricky to tell if an actual value has been converted or if junk has been passed to atoi().

    I'm not a huge fan of this way to handle errors.

I'll update as other options cross my mind...

Upvotes: 45

Evan Teran
Evan Teran

Reputation: 90422

a common way is to pass a pointer to a success flag like this:

int my_function(int *ok) {
    /* whatever */
    if(ok) {
        *ok = success;
    }
    return ret_val;
}

call it like this:

int ok;
int ret = my_function(&ok);
if(ok) {
    /* use ret safely here */
}

EDIT: example implementation here:

s32 intval(const char *string, int *ok) {
    bool negative = false;
    u32 current_char = 0;

    if (string[0] == '-') {
        negative = true;
        current_char = 1;
    }

    s32 num = 0;
    while (string[current_char]) {
        if (string[current_char] < '0' || string[current_char] > '9') {
                // Return an error here.. but how?
                if(ok) { *ok = 0; }
        }

        num *= 10;
        num += string[current_char] - '0';
        current_char++;
    }

    if (negative) {
        num = -num;
    }
    if(ok) { *ok = 1; }
    return num;
}

int ok;
s32 val = intval("123a", &ok);
if(ok) {
    printf("conversion successful\n");
}

Upvotes: 14

quinmars
quinmars

Reputation: 11563

In general I prefer the way Jon Skeet proposed, ie. returning a bool (int or uint) about success and storing the result in a passed address. But your function is very similar to strtol, so I think it is a good idea to use the same (or similar) API for your function. If you give it a similar name like my_strtos32, this makes it easy to understand what the function does without any reading of the documentation.

EDIT: Since your function is explicitly 10-based, my_strtos32_base10 is a better name. As long as your function is not a bottle-neck you can then, skip your implementation. And simply wrap around strtol:


s32
my_strtos32_base10(const char *nptr, char **endptr)
{
    long ret;
    ret = strtol(nptr, endptr, 10);
    return ret;
}

If you later realize it as an bottleneck you can still optimize it for your needs.

Upvotes: 3

Darian Miller
Darian Miller

Reputation: 8088

You can either return an instance of a class where a property would be the value interested in, another property would be a status flag of some sort. Or, pass in an instance of the result class..

Pseudo code
  MyErrStatEnum = (myUndefined, myOK, myNegativeVal, myWhatever)

ResultClass
  Value:Integer;
  ErrorStatus:MyErrStatEnum

Example 1:

result := yourMethod(inputString)

if Result.ErrorStatus = myOK then 
   use Result.Value
else
  do something with Result.ErrorStatus

free result

Example 2

create result
yourMethod(inputString, result)

if Result.ErrorStatus = myOK then 
   use Result.Value
else
  do something with Result.ErrorStatus

free result

The benefit of this approach is you can expand the info coming back at any time by adding additional properties to the Result class.

To expand this concept further, it also applies to method calls with multiple input parameters. For example, instead of CallYourMethod(val1, val2, val3, bool1, bool2, string1) instead, have a class with properties matching val1,val2,val3,bool1,bool2,string1 and use that as a single input parameter. It cleans up the method calls and makes the code more easily modified in the future. I'm sure you've seen that method calls with more than a few parameters is much more difficult to use/debug. (7 is the absolute most I would say.)

Upvotes: 1

S.Lott
S.Lott

Reputation: 391854

The os-style global errno variable is also popular. Use errno.h.

If errno is non-zero, something went wrong.

Here's a man page reference for errno.

Upvotes: 8

Jon Skeet
Jon Skeet

Reputation: 1500495

Well, the way that .NET handles this in Int32.TryParse is to return the success/failure, and pass the parsed value back with a pass-by-reference parameter. The same could be applied in C:

int intval(const char *string, s32 *parsed)
{
    *parsed = 0; // So that if we return an error, the value is well-defined

    // Normal code, returning error codes if necessary
    // ...

    *parsed = num;
    return SUCCESS; // Or whatever
}

Upvotes: 18

Related Questions