Errors in C

In C, error handling is typically done through return values, similar to the approach shown in the original example. However, C doesn’t have built-in error types or exception handling, so we’ll use integer return values to indicate errors.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// By convention, we'll use negative integers to represent errors
#define SUCCESS 0
#define ERR_CANT_WORK_WITH_42 -1
#define ERR_OUT_OF_TEA -2
#define ERR_POWER -3

// Function that may return an error
int f(int arg, int* result) {
    if (arg == 42) {
        return ERR_CANT_WORK_WITH_42;
    }
    *result = arg + 3;
    return SUCCESS;
}

// Function to simulate making tea
int makeTea(int arg) {
    if (arg == 2) {
        return ERR_OUT_OF_TEA;
    } else if (arg == 4) {
        return ERR_POWER;
    }
    return SUCCESS;
}

// Helper function to get error message
const char* getErrorMessage(int error) {
    switch(error) {
        case ERR_CANT_WORK_WITH_42:
            return "can't work with 42";
        case ERR_OUT_OF_TEA:
            return "no more tea available";
        case ERR_POWER:
            return "can't boil water";
        default:
            return "unknown error";
    }
}

int main() {
    int result;
    int error;
    
    int inputs[] = {7, 42};
    for (int i = 0; i < 2; i++) {
        error = f(inputs[i], &result);
        if (error != SUCCESS) {
            printf("f failed: %s\n", getErrorMessage(error));
        } else {
            printf("f worked: %d\n", result);
        }
    }

    for (int i = 0; i < 5; i++) {
        error = makeTea(i);
        if (error != SUCCESS) {
            if (error == ERR_OUT_OF_TEA) {
                printf("We should buy new tea!\n");
            } else if (error == ERR_POWER) {
                printf("Now it is dark.\n");
            } else {
                printf("unknown error: %s\n", getErrorMessage(error));
            }
            continue;
        }
        printf("Tea is ready!\n");
    }

    return 0;
}

In this C version:

  1. We define error codes as negative integers, which is a common convention in C.

  2. The f function now takes a pointer to an int to store the result, and returns an error code.

  3. We use a separate getErrorMessage function to map error codes to error messages, simulating the behavior of error types in higher-level languages.

  4. The makeTea function returns error codes directly.

  5. In the main function, we check the return values of our functions to handle errors.

  6. We use switch statements to handle different error cases, similar to how errors.Is is used in the original example.

This approach mimics the error handling style of the original code as closely as possible within the constraints of the C language. While it’s not as elegant as Go’s error handling, it follows similar principles of explicit error checking and handling.

To compile and run this program:

$ gcc -o errors errors.c
$ ./errors
f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

This output should match the behavior of the original program.