The Developer’s Cry

Yet another blog by a hobbyist programmer

When Some is None: the Option type in C

Functional programming has a feature to express whether a variable contains a valid value or not; the Option type (sometimes called Maybe) either has Some(value), or it is None. Can your C do that? At first sight it somewhat resembles NULL in C, but that’s not the same thing.

The Option type is convenient for expressing invalid return values. A prime example is the (flawed) atoi() function, that converts a string to an integer. The function is flawed because its return value fails to properly indicate an invalid conversion. The C library fixes that by providing strtol(), which does indicate any parse errors. Functional programming offers a more elegant solution by returning an Option.

The Option is a parametric type, therefore best implemented in C++ using templates. In C we don’t have templates; the best we can do is using a macro:

#define Option(T)       Option_##T
#define decl_Option(T)  typedef struct { T value; bool none; } Option(T)
#define Some(x,y)       ((y) = (x).value, !(x).none)
#define None(x)         ((x).none)

The decl_Option(T) macro does not work well for pointer types; the macro expands to a syntax error when using the * asterisk notation. But you know, NULL is good enough for me for signaling an invalid pointer. A more pedantically correct way to do it, is using nullptr in C23.

The Some and None macros are to be used in an if-statement. Note that Some assigns the value even when it is invalid; although ugly, this also means that the variable will be overwritten with a default (e.g. zero, or minus one) in case it is invalid, and that is good practice.

Now we can implement an atoi()-like function that returns an Option as such:

decl_Option(int);

Option(int) str_as_int(const char* s) {
    if (s == NULL || !*s) {
        return (Option(int)){.value = 0, .none = true};
    }

    char* endptr = NULL;
    long value = strtol(s, &endptr, 0);
    if (*endptr
        || errno == EINVAL || errno == ERANGE
        || value > INT_MAX || value < INT_MIN) {
        return (Option(int)){.value = 0, .none = true};
    }
    return (Option(int)){.value = (int)value, .none = false};
}

When we call this function, we get an Option(int) in return. We can test the Option for Some or None.

Option(int) opt = str_as_int("0x2a");
int n;
if (Some(opt, n)) {
    printf("%d\n", n);
}

This prints the value 42. Conversely, we can test for None:

Option(int) opt = str_as_int("xxx");
if (None(opt)) {
    printf("opt is None\n");
}

Arguably our str_as_int() function should have returned a Result instead. A Result is a type that can indicate different kinds of errors. We can declare a Result type in C by using the same trick as with Option. But you know what, I won’t show it here.

C is not a functional language, and such constructs do not always translate well. We could have taken a far more simpler approach (*throws hands up in the air*). It is more idiomatic to write:

bool str_to_int(const char* s, int* pvalue);

if (!str_to_int("xxx", &n)) {
    printf("invalid number!\n");
}

So in the end, I think we can conclude that yes, it is possible to have the functional Option type in C—but it is not pretty, and it is not better. This goes to show that the functional construct, however pretty in a purely functional context, is unnecessary in the space of C.