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.