PATTERN Cited by 1 source
Centralized forward declarations (Fwd.h per directory)¶
Shape¶
When a codebase chooses concepts/forward-declaration over #include
as the default strategy for breaking C++ header include chains, engineers
face a maintenance tax: forward declarations scattered across files hurt
readability, searchability (grep "struct Foo" now returns N files),
and rename safety. The naive answer — let each file forward-declare
whatever it needs — doesn't scale beyond a small codebase.
Centralized forward declarations is Figma's convention:
- One
Fwd.hper directory. The file lists every forward declaration required by other files within that directory (or exported to dependents). - Every header in the directory
#includes itsFwd.h. So forward- declared symbols are available to all headers without any per-file bookkeeping. - Source (
.cpp) files never#includeFwd.h. Forward declarations are valuable in headers because they prevent including files from pulling in the forward-declared symbol's defining header transitively. Source files are leaves in the include graph — forward-declaring there saves nothing.
Example (from the Figma post):
// AnimalFwd.h
namespace Figma {
struct Animal;
struct Dog;
struct Cat;
enum struct AnimalType;
using Feline = Cat;
};
Why it works¶
- One place per directory to discover / rename.
Fwd.his the authoritative list; renames update one location. - Engineers don't have to think about forward declarations day-to-day.
Directory-scoped
Fwd.hmeans the pattern applies organically — adding a new type and using it from another file in the same directory "just works" without the author deciding header-vs-forward-declaration. - Directory = unit of cohesion. Figma's codebase is structured
"something resembling modules, with each directory being built
independently of others."
Fwd.hrides on that — the types most frequently forward-declared from files in a directory are likely defined within the directory or a near peer.
Composition with the measurement gate¶
Fwd.h is the recipe engineers apply when systems/includes-py
flags a transitive-byte regression in CI and the offending include is
genuinely used. The include stays (so DIWYDU doesn't flag it), but the
header's #include "foo.h" can often be replaced by
#include "fooFwd.h" when only pointers / references / signatures of
Foo are needed. The pattern makes the fix fast and conventional: no
per-file forward-declaration debate.
Trade-offs¶
- Readability-within-header. Forward-declaring symbols whose
definitions live elsewhere can make a header harder to read in
isolation:
struct Dog : Animal { ... }whereAnimalis only a forward declaration requires the reader to go look it up. - Searchability. Centralizing mitigates but doesn't eliminate:
struct Animalstill appears inAnimalFwd.hand whereverAnimalis truly defined. - Forward-declaration limits. Any user of
Foothat needssizeof, inheritance, or member access still needs the real header.Fwd.hdoesn't eliminate heavyweight includes — it eliminates the avoidable ones. - Directory boundary friction. A type forward-declared in
adirFwd.hbut heavily used inbdir/files is not served by the pattern in its pure form. Resolutions: export theFwd.hpublicly, or put the forward declaration in the consumer's directory too.
Where it composes in the wiki¶
- patterns/ci-regression-budget-gate — the pattern that measures the
cost;
Fwd.his one of the two canonical recipes for fixing a CI-gate flag (the other is just deleting unused includes, which systems/diwydu catches). - concepts/c-plus-plus-compilation-model — the reason forward declarations save bytes in the first place.
- concepts/forward-declaration — the raw primitive this pattern formalizes at codebase scope.
Seen in¶
- sources/2024-04-27-figma-speeding-up-c-build-times — canonical
description of the
Fwd.h-per-directory rule and the "never include from source files" corollary.