Fluent Interfaces for C

Warning: The following is an experiment more than anything, and a slightly masochistic one to probe the limits of what possible. More often than not Fluent interfaces fall into to the category of clever code that should be avoided in favour of stupid, simple code. This especially true of assert libraries I'll be using as an example here, where they add more complication and deliver no value.

The Good

My goal was to create something that resembled AssertJ I was actually quite happy with the result of the whole endeavour, ending up with a more or less identical result, at least on the surface:

void main(int argc, char** argv) {
    assertThat("foo").startsWith("f").equals("foo");
    assertThat(57).greaterThan(4).lessThan(100).equals(55);
}

The bad

The implementation is mostly straightforward. First there's the public interface, basically just a collection of function pointers and the typed value:

struct _stringContext {
    struct _stringContext (*equals)(const char*);
    struct _stringContext (*startsWith)(const char*);
    const char* value;
} stringContext;
typedef struct _stringContext StringContext;

Next there's the state handling which is the biggest source of issues. Without a compiler managing an implicit this pointer it has to be handled manually, rather than changing the API to pass along the state explicitly it is stored in the static stringContext variable, creating several potential issues.

The actual assert functions are incredibly simple, they assume the context object is created (they can't be called before it is) and return it (or another context). Returning the context is what allows the methods to be chained.

StringContext string_equals(const char* expected) {
    assert(!strcmp(stringContext.value, expected));
    return stringContext;
}

The context themselves are created in the *_assertThat methods:

StringContext string_assertThat(const char* value) {
    StringContext context = {
        .value = value,
        .equals = string_equals,
        .startsWith = string_startsWith
    };
    stringContext = context;
    return stringContext;
}

And finally there's the last source of problems but what ties everything together, the assertThat _Generic macro:

#define assertThat(x) _Generic((x), \
    int: int_assertThat((intmax_t)x), \
    char*: string_assertThat((char*)x) \
) 

This is probably the most interesting snippet of the exercise, many people don't know that C has generics and the implementation is very different to most languages. They're simultaneously more and less powerful while being both harder and easier to use. But basically if the assertThat macro is passed an int it will expand to int_assertThat and if it's passed a char* it will expand to string_assertThat.

There are some other caveats too, the casting is important because, due to some weirdness beyond my pay grade, every possible expansion has to be evaluated. This is especially problematic when you're mixing pointers with int types. This is is mostly just to control superfluous warnings though, the compiler seems to always do the right thing.

The Ugly

The state management is the worst part as it opens up several possible error scenarios. In exploring these scenarios it became apparent that some of them were even issues in high level languages. The most obvious one is multi threading, this is probably the easiest though, slap thread local on there and hopefully be done with it.

Others fall more into the realm of API misuse, consider the following code:

int otherFunc() {
    assertThat(42);
    return 57;
}

void main(int argc, char** argv) {
    assertThat(57).equals(otherFunc());
}

From quickly looking at the code it seems like this should pass, but the assert in otherFunc has changed the state so equals is now checking if 57 == 42. With some fluent API's you could put these contexts on a stack and this scenario would work, but in this case there is no finalization step, so there is no logical place to pop the stack. The next case is similar but applies to other languages too:

int otherFunc() {
    assertThat(42);
    return 57;
}

void main(int argc, char** argv) {
    IntContext contextA = assertThat(57);
    IntContext contextB = assertThat(42);
    contextA.equals(57);
}

With a fluent API there is nothing stopping the calling code from holding on to the context. In this case they'll get the wrong result, but imagine if this code had side effects, like a registerConfigInContainer() method. Fluent interfaces can't prevent this kind of error, they aren't as safe as they often feel.

As is usually the case when you try to implement something in C and strip away all the classes, the interfaces and other abstractions found in higher level languages you can see what's going on underneath a lot more clearly. In this case it's remarkably simple, a fluent interface can be boiled down to little more than a series of structures containing function pointers. This is still somewhat enlightening though, if I ever found myself implementing one in a high level language again my solution will be much simpler structure of function pointers and no the mess of interfaces of created in the past.