summaryrefslogtreecommitdiffstats
path: root/_posts/2017-06-05-Limited-generics-in-C.md
blob: 17b9c9ea3544869e884cfab4bd758190ec50738e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
---
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](https://github.com/SirCmpwn/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.