Exceptions in C

Exception handling in C doesn't exist. What's used instead to signify something went wrong isn't set in stone but usually consists of returning an invalid error value (such as a negative number for something that can't normally produce negative numbers) and hoping whoever uses your code remembers to check for them. Unfortunately, this is effectively what we'll have to do too.

But since we're in Python we can provide wrapper functions which turn our flimsy error codes into proper Python exceptions which don't need to be continuously tested for. Before we do that, however, we need to communicate back to Python that something has gone wrong. This can be easy or very fiddly.

If your function currently returns nothing then you can just get it to return an is ok* boolean. If you have more than one way a function can go wrong then you may wish to use a cslug.Header to define and share a series of error codes for each case. If the function does already return something then you'll have to use the return multiple values from a function trick to give it a second return variable.

The following is an example that shows all the above, using an automatically generated header file to share status constants and ctypes.byref() to provide both a status output as well as the intended output of a function.

pedantic-log.c
#include <math.h>
#include "status-codes.h"

int pedantic_log(double in, double * out) {
  if (in == 0) return LOG_OF_0_ERROR;
  if (in < 0) return LOG_OF_NEGATIVE_VALUE_ERROR;
  *out = log(in);
  return OK;
}
accompanying Python code
import enum
import ctypes
from cslug import CSlug, Header


class Status(enum.Enum):
    """Status codes to indicate different error types."""
    OK = enum.auto()
    LOG_OF_0_ERROR = enum.auto()
    LOG_OF_NEGATIVE_VALUE_ERROR = enum.auto()


slug = CSlug("pedantic-log.c", headers=Header("status-codes.h", defines=Status))


def log(x):
    """Fussy natural logarithm which raises exceptions for invalid inputs."""

    out = ctypes.c_double()
    status = Status(slug.dll.pedantic_log(x, ctypes.byref(out)))

    if status is not Status.OK:
        # If you're feeling especially motivated you could write a custom error
        # message for each outcome - I am not feeling such a motivation...
        error = status.name.replace("_", " ").lower()
        raise ValueError(f"log({x}) triggered a {error}.")

    return out.value