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 wlr_output { const struct wlr_output_impl impl; struct wlr_output_state state; // […] };

void wlr_output_enable(struct wlr_output output, bool enable); bool wlr_output_set_mode(struct wlr_output output, struct wlr_output_mode mode); void wlr_output_destroy(struct wlr_output 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 wlr_output wlr_output_create(struct wlr_output_impl impl, struct wlr_output_state state); void wlr_output_free(struct wlr_output 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 wlr_drm_output_enable(struct wlr_output_state output, bool enable) { struct wlr_backend_state state = wl_container_of(output->renderer, state, renderer); if (output->state != DRM_OUTPUT_CONNECTED) { return; } if (enable) { drmModeConnectorSetProperty(state->fd, output->connector, output->props.dpms, DRM_MODE_DPMS_ON); // […] } else { drmModeConnectorSetProperty(state->fd, output->connector, output->props.dpms, DRM_MODE_DPMS_STANDBY); } }

// […] struct wlr_output output = wlr_output_create(&output_impl, output); ```

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.