logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Limited-generics-in-C.md (3402B)


  1. ---
  2. date: 2017-06-05
  3. layout: post
  4. title: Limited "generics" in C without macros or UB
  5. tags: [C]
  6. ---
  7. I should start this post off by clarifying that what I have to show you today is
  8. not, in fact, generics. However, it's useful in some situations to solve the
  9. same problems that generics might. This is a pattern I've started using to
  10. reduce the number of `void*` pointers floating around in my code: multiple
  11. definitions of a struct.
  12. **Errata**: we rolled this approach back in wlroots because it causes problems
  13. with LTO. I no longer recommend it.
  14. Let's take a look at a specific example. In
  15. [wlroots](https://github.com/SirCmpwn/wlroots), `wlr_output` is a generic type
  16. that can be implemented by any number of backends, like DRM (direct rendering
  17. manager), wayland windows, X11 windows, RDP outputs, etc. The `wlr/types.h`
  18. header includes this structure:
  19. ```c
  20. struct wlr_output_impl;
  21. struct wlr_output_state;
  22. struct wlr_output {
  23. const struct wlr_output_impl *impl;
  24. struct wlr_output_state *state;
  25. // [...]
  26. };
  27. void wlr_output_enable(struct wlr_output *output, bool enable);
  28. bool wlr_output_set_mode(struct wlr_output *output,
  29. struct wlr_output_mode *mode);
  30. void wlr_output_destroy(struct wlr_output *output);
  31. ```
  32. `wlr_output_impl` is defined elsewhere:
  33. ```c
  34. struct wlr_output_impl {
  35. void (*enable)(struct wlr_output_state *state, bool enable);
  36. bool (*set_mode)(struct wlr_output_state *state,
  37. struct wlr_output_mode *mode);
  38. void (*destroy)(struct wlr_output_state *state);
  39. };
  40. struct wlr_output *wlr_output_create(struct wlr_output_impl *impl,
  41. struct wlr_output_state *state);
  42. void wlr_output_free(struct wlr_output *output);
  43. ```
  44. Nowhere, however, is `wlr_output_state` defined. It's left an incomplete type
  45. throughout all of the common `wlr_output` code. The "generic" part is that each
  46. output implementation, in its own private headers, defines the
  47. `wlr_output_state` struct for itself, like the DRM backend:
  48. ```c
  49. struct wlr_output_state {
  50. uint32_t connector;
  51. char name[16];
  52. uint32_t crtc;
  53. drmModeCrtc *old_crtc;
  54. struct wlr_drm_renderer *renderer;
  55. struct gbm_surface *gbm;
  56. EGLSurface *egl;
  57. bool pageflip_pending;
  58. enum wlr_drm_output_state state;
  59. // [...]
  60. };
  61. ```
  62. This allows implementations of the `enable`, `set_mode`, and `destroy` functions
  63. to avoid casting a `void*` to the appropriate type:
  64. ```c
  65. static struct wlr_output_impl output_impl = {
  66. .enable = wlr_drm_output_enable,
  67. // [...]
  68. };
  69. static void wlr_drm_output_enable(struct wlr_output_state *output,
  70. bool enable) {
  71. struct wlr_backend_state *state =
  72. wl_container_of(output->renderer, state, renderer);
  73. if (output->state != DRM_OUTPUT_CONNECTED) {
  74. return;
  75. }
  76. if (enable) {
  77. drmModeConnectorSetProperty(state->fd,
  78. output->connector,
  79. output->props.dpms,
  80. DRM_MODE_DPMS_ON);
  81. // [...]
  82. } else {
  83. drmModeConnectorSetProperty(state->fd,
  84. output->connector,
  85. output->props.dpms,
  86. DRM_MODE_DPMS_STANDBY);
  87. }
  88. }
  89. // [...]
  90. struct wlr_output output = wlr_output_create(&output_impl, output);
  91. ```
  92. The limitations of this approach are apparent: you cannot work with multiple
  93. definitions of `wlr_output_state` in the same file. However, you get improved
  94. type safety, have to write less code, and improve readability.