167 lines
5.9 KiB
Text
167 lines
5.9 KiB
Text
title = "header files are cool, actually"
|
|
|
|
+++
|
|
|
|
You know what.
|
|
I actually kinda like header files.
|
|
|
|
Yeah, _those_ things.
|
|
From C and C++.
|
|
|
|
I've heard lots of opinions from fellow programmers, saying it's annoying to have to update your signatures in _two_ places.
|
|
However, I believe a lot of the extra repetition comes from _the bad design of C++'s classes_, rather than the idea of header files itself.
|
|
|
|
I've mentioned this briefly before in [C++ without Classes][page:programming/cxx-without-classes], but a lot of redundant editing is born out of C++'s poor implementation of class encapsulation.
|
|
In C++, your implementation details come out leaking into the header file.
|
|
|
|
```cpp
|
|
// .h file
|
|
|
|
class World
|
|
{
|
|
public:
|
|
Entity& entity(Entity_Id id);
|
|
|
|
void update(const Update_Context& cx);
|
|
void draw(const Draw_Context& cx);
|
|
|
|
private:
|
|
void draw_entities(const Draw_Context& cx);
|
|
|
|
Entity entities[256];
|
|
// etc
|
|
};
|
|
|
|
// .cpp file
|
|
|
|
Entity& World::entity(Entity_Id id) { /* ... */ }
|
|
|
|
void World::update(const Update_Context& cx) { /* ... */ }
|
|
|
|
void World::draw(const Draw_Context& cx) { /* ... */ }
|
|
|
|
void World::draw_entities(const Draw_Context& cx) { /* ... */ }
|
|
```
|
|
|
|
Closing yourself in the bubble of C++ might make you think header files are just that.
|
|
Annoying nuisances, artifacts of language design of the past.
|
|
DRY killers.
|
|
|
|
Hacks of the dated C compilation model.
|
|
|
|
However, if you observe what header files (or their equivalents) look like in _other_ languages---you start seeing something different.\
|
|
Consider the same example, but written in C.
|
|
|
|
```c
|
|
typedef struct
|
|
{
|
|
Entity entities[256];
|
|
// etc
|
|
} World;
|
|
|
|
Entity* world_entity(World* w, Entity_Id id);
|
|
|
|
void world_update(World* w, const Update_Context* cx);
|
|
void world_draw(const World* w, const Update_Context* cx);
|
|
```
|
|
|
|
See the difference? `draw_entities` is nowhere to be seen.
|
|
Meanwhile, in the corresponding implementation file:
|
|
|
|
```c
|
|
Entity* world_entity(World* w, Entity_Id id) { /* ... */ }
|
|
|
|
void world_update(World* w, const Update_Context* cx) { /* ... */ }
|
|
|
|
static void draw_entities(const World* w, const Update_Context* cx) { /* ... */ }
|
|
|
|
void world_draw(const World* w, const Update_Context* cx) { /* ... */ }
|
|
```
|
|
|
|
The `draw_entities` implementation detail---a _private function_---is still very much there!
|
|
|
|
In OCaml, you would do something similar.
|
|
Here's `world.mli`:
|
|
|
|
```ocaml
|
|
type t
|
|
|
|
val entity : t -> Entity.id -> Entity.t
|
|
val update : t -> Game_loop.update_context -> unit
|
|
val draw : t -> Game_loop.draw_context -> unit
|
|
```
|
|
|
|
And here's `world.ml`:
|
|
|
|
```ocaml
|
|
type data = {
|
|
entities: Entity.t array;
|
|
}
|
|
type t = data ref
|
|
|
|
let entity w id = (* ... *)
|
|
|
|
let update w cx = (* ... *)
|
|
|
|
let draw_entities w cx = (* ... *)
|
|
|
|
let draw w cx = (* ... *)
|
|
```
|
|
|
|
(Disclaimer: my knowledge of OCaml is pretty surface level. This is probably not valid syntax.)
|
|
|
|
Our implementation detail `draw_entities` appears again, but only in the module's interface file.
|
|
|
|
And that's how I view header files: as _public interface declarations_.
|
|
They say, "this is the set of public procedures you can run on these types."
|
|
|
|
When viewed this way, header files become extremely useful for tracking changes in a module's public API, semantic versioning-wise.
|
|
|
|
But perhaps more importantly, because headers written with the "public interface" philosophy in mind are so laser-focused on exposing the... well, _public interface_, they become your module's _reference documentation._
|
|
|
|
My favourite example of this is [Dear ImGui](https://github.com/ocornut/imgui/blob/v1.92.1/imgui.h), which takes this idea to the extreme.
|
|
Dear ImGui needs no documentation generator like Doxygen.
|
|
It's all right there in the header file, and all you have to do is open it in your text editor and grep for whatever you need at the moment.
|
|
|
|
Here's an excerpt of `imgui.h` (reformatted to fit my blog's column limit better):
|
|
|
|
```c
|
|
// Windows
|
|
// - Begin() = push window to the stack and start appending to it.
|
|
// End() = pop window from the stack.
|
|
// - Passing 'bool* p_open != NULL' shows a window-closing widget in
|
|
// the upper-right corner of the window, which clicking will set
|
|
// the boolean to false when clicked.
|
|
// - You may append multiple times to the same window during
|
|
// the same frame by calling Begin()/End() pairs multiple times.
|
|
// Some information such as 'flags' or 'p_open' will only be
|
|
// considered by the first call to Begin().
|
|
// - Begin() return false to indicate the window is collapsed or fully
|
|
// clipped, so you may early out and omit submitting anything to
|
|
// the window. Always call a matching End() for each Begin() call,
|
|
// regardless of its return value!
|
|
// [Important: due to legacy reason, Begin/End and BeginChild/EndChild
|
|
// are inconsistent with all other functions such as BeginMenu/EndMenu,
|
|
// BeginPopup/EndPopup, etc. where the EndXXX call should only be
|
|
// called if the corresponding BeginXXX function returned true.
|
|
// Begin and BeginChild are the only odd ones out.
|
|
// Will be fixed in a future update.]
|
|
// - Note that the bottom of window stack always contains a window
|
|
// called "Debug".
|
|
IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0);
|
|
IMGUI_API void End();
|
|
```
|
|
|
|
Compare this to grepping through an `.rs` file, which usually yields lots of unrelated results.
|
|
I pretty much never grep through Rust files to look at APIs, while I do it all the time in C++.
|
|
|
|
Perhaps it's a sign that the complexity of JavaScript-heavy online HTML documentation viewers with mediocre search functionality is entirely self-inflicted.\
|
|
(I'll give them though that rich text is pretty cool, and Hackage's global type signature search is _wicked_ cool. Nothing that a sufficiently advanced LSP server shouldn't be capable of doing, though.)
|
|
|
|
(...and also, fuck Doxygen. I hate using Doxygen so much. But I'll leave that for another time.)
|
|
|
|
---
|
|
|
|
Either way, that does it for The Ramblings I Will Link Whenever Someone Mentions Header Files Are The Bane Of Their Existence.
|
|
|
|
Thank you for reading.
|