sl/plan9 OK, uriel.


date: 2017-06-05 layout: post title: Limited "generics" in C without macros or UB tags: [C]

I should start this post off by clarifying that what I have to show you today is not, in fact, generics. However, it's useful in some situations to solve the same problems that generics might. This is a pattern I've started using to reduce the number of void* pointers floating around in my code: multiple definitions of a struct.

Errata: we rolled this approach back in wlroots because it causes problems with LTO. I no longer recommend it.

Let's take a look at a specific example. In wlroots, wlr_output is a generic type that can be implemented by any number of backends, like DRM (direct rendering manager), wayland windows, X11 windows, RDP outputs, etc. The wlr/types.h header includes this structure:

c struct wlr_output_impl; struct wlr_output_state;

struct wlroutput { const struct wlroutputimpl impl; struct wlroutputstate state; // [...] };

void wlroutputenable(struct wlroutput output, bool enable); bool wlroutputsetmode(struct wlroutput output, struct wlroutputmode mode); void wlroutputdestroy(struct wlroutput output); ”

wlr_output_impl is defined elsewhere:

c struct wlr_output_impl { void (*enable)(struct wlr_output_state *state, bool enable); bool (*set_mode)(struct wlr_output_state *state, struct wlr_output_mode *mode); void (*destroy)(struct wlr_output_state *state); };

struct wlroutput wlroutputcreate(struct wlroutputimpl impl, struct wlroutputstate state); void wlroutputfree(struct wlroutput output); ”

Nowhere, however, is wlr_output_state defined. It's left an incomplete type throughout all of the common wlr_output code. The "generic" part is that each output implementation, in its own private headers, defines the wlr_output_state struct for itself, like the DRM backend:

`c struct wlr_output_state { uint32_t connector; char name[16]; uint32_t crtc; drmModeCrtc *old_crtc; struct wlr_drm_renderer *renderer; struct gbm_surface *gbm; EGLSurface *egl; bool pageflip_pending; enum wlr_drm_output_state state; // [...] }; ”`

This allows implementations of the enable, set_mode, and destroy functions to avoid casting a void* to the appropriate type:

c static struct wlr_output_impl output_impl = { .enable = wlr_drm_output_enable, // [...] };

static void wlrdrmoutputenable(struct wlroutputstate output, bool enable) { struct wlrbackendstate state = wlcontainerof(output->renderer, state, renderer); if (output->state != DRMOUTPUTCONNECTED) { return; } if (enable) { drmModeConnectorSetProperty(state->fd, output->connector, output->props.dpms, DRMMODEDPMSON); // ...

// ...

The limitations of this approach are apparent: you cannot work with multiple definitions of wlr_output_state in the same file. However, you get improved type safety, have to write less code, and improve readability.