From d8d3ebdd3896cb0ecba59818bb4893e5b6637d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Tue, 5 Aug 2025 23:50:54 +0200 Subject: [PATCH] new post: header files are cool, actually --- content/h.dj | 167 +++++++++++++++++++++++++++++++++++++ content/treehouse/new.tree | 15 +++- 2 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 content/h.dj diff --git a/content/h.dj b/content/h.dj new file mode 100644 index 0000000..4b083a9 --- /dev/null +++ b/content/h.dj @@ -0,0 +1,167 @@ +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. diff --git a/content/treehouse/new.tree b/content/treehouse/new.tree index 14343c1..ef76a8a 100644 --- a/content/treehouse/new.tree +++ b/content/treehouse/new.tree @@ -26,6 +26,13 @@ if you've been wondering what I've been up to, you've come to the right place. if you want to read any of the posts, follow the links. it's like that by design. +% tags = ["programming", "cxx"] + id = "01K1Y3G5N1KGCN1E9B36QTYMSZ" +- ### [header files are cool, actually][page:h.dj] + + % id = "01K1Y3G5N1WTEYF5X4JJHGR5XN" + - in which I ramble about how to write good header files. + % tags = ["meow", "shower"] id = "01K05F3E3DN1PY9ZWN98ZE5HVV" - ### [furry! ---w--- (version 2)][page:furry.dj] @@ -201,7 +208,7 @@ if you've been wondering what I've been up to, you've come to the right place. % id = "01JDJJSEWAVZGJN3PWY94SJMXT" - I recently rebuilt the treehouse to use a virtual file system for its source and target directories. - + % id = "01JDJJSEWA7K5T3Z0Y6NQ8RBGX" - this is an exploration of how I built my abstraction, how it works, and what I learned from it. @@ -232,7 +239,7 @@ if you've been wondering what I've been up to, you've come to the right place. % id = "01JDTBGSJ7KB9GYWQZ8G9D97NY" - I'm an adorable little cat boy. purrow! _snuggle snuggle_ - + % id = "01JDTBGSJ7RVYR0VD693FN10QH" - a lesson in Shock Therapy & Getting Over It {-with Bennett Foddy-} @@ -332,7 +339,7 @@ if you've been wondering what I've been up to, you've come to the right place. % id = "01J293BFEB4G7214N20SZA8V7W" - sometimes people call me crazy for saying that bashing JavaScript is senseless and that it's not as bad of a language as people make it out to be. - + % id = "01J293BFEBYSW4K7YHVN42J3WP" - so I decided to collect my thoughts into a nice little page I can link easily. @@ -409,7 +416,7 @@ if you've been wondering what I've been up to, you've come to the right place. % id = "01HR9ZTS8RY3N4EJM5W7WBTF0G" - sidebars! also known as, _"enjoying the main content? how about I distract you from it so that you can't focus!"_ - + % id = "01HR9ZTS8RQ1EN0THYEVNQRY2A" - seriously though. I don't like them.