Enhanced C Coding Style Guide (Version 1.0)
For a compact summary, see the Enhanced C Coding Style Cheat Sheet.
1. Scope
This guide extends the standard C Coding Style Guide. The standard guide remains authoritative for general formatting, naming, line breaking, comments, control flow, and function layout.
This guide covers the enhanced C syntax additions.
2. File Prologue Macros
Header files and implementation files start with the project wrappers, not with raw nullability or helper includes.
Header files:
#include "begin_header.h"
begin_header
// declarations
end_header
#include "end_header.h"
Implementation files:
#include "begin_impl.h"
begin_impl
// definitions
end_impl
Headers use both begin_header and end_header, and finish with #include "end_header.h".
Implementation files use begin_impl and may end with end_impl, but do not include end_header.h.
Do not include begin_common.h, end_common.h, begin_ptr.h, end_ptr.h, begin_integer.h, end_integer.h, begin_enum.h, end_enum.h, begin_cpp.h, or end_cpp.h directly in normal project code. Use the file prologue macros.
The wrappers provide the common integer, enum, pointer, preprocessor, and helper macros.
The wrappers also delimit the nonnull assumption region. Inside begin_header / end_header and begin_impl / end_impl, pointer types are assumed to be nonnull unless marked otherwise.
This default nonnull model works well for ordinary pointers, but not for pointer chains such as **, where each level may need its own explicit nullability. For output pointers prefer the OutPtr...(...) macros. For other pointer chains use _req and _opt explicitly.
3. Type Inference Macros
Prefer the project inference macros over spelling out repeated local types when the type is obvious from the initializer.
def name = expr; declares a const local with inferred type.
set name = expr; declares a mutable local with inferred type.
Prefer def unless mutation is required.
Do not use them where the inferred type is unclear from local context.
Examples:
def count = arrayCount;
set index = 0;
4. Guard and Early Exit Macros
Prefer the shared early-exit macros over hand-written repetitive nil/NULL checks when they express the same control flow clearly.
Use return_unless(...) for early returns.
Use continue_unless(...) for loop iteration skips.
Use break_unless(...) for loop termination.
Use guard(...) { ... } endguard when code should run only if one or more assignments, and optionally a trailing condition, succeed.
These macros are preferable to repeating assignment, check, and branch boilerplate by hand.
When these macros bind values, they use def. Treat the bound names as const locals.
Examples:
return_unless(NULL, ptr);
return_unless(false, file, OpenFile(path), file->isValid);
continue_unless(item, NextItem(iterator), item->isEnabled);
break_unless(record, FindRecord(table, key));
guard(buffer, CreateBuffer(size), size > 0) {
UseBuffer(buffer);
}
endguard
5. Requirements and Assertions
Use the project requirement and assertion macros instead of the plain C library variants.
Use require(...) for conditions that must hold in production.
Use requireFail(...) when the condition is implicitly false and the failure must also remain active in production.
Use assert(...) for debug-only invariants.
Use assertFail(...) when the condition is implicitly false in a debug-only invariant.
Prefer the message variants when failure context would otherwise be unclear.
Do not mix in raw assert() from the standard library in project code.
Examples:
require(size > 0);
require(fd >= 0, "open failed for %s", path);
requireFail("unexpected parser state");
assert(index < count);
assertFail("unexpected token kind %d", tokenKind);
6. Integer Types
Prefer the project integer aliases over raw C integer types unless an external API or standard type name is required.
The integer aliases distinguish both width semantics and signedness.
Unsigned is the default. int8, int16, int32, and int64 are unsigned fast integer types.
Signed types are explicitly marked with a leading s, such as sint8, sint16, sint32, and sint64.
Exact-width types add e, such as int32e and sint32e.
Minimum-width types add m, such as int32m and sint32m.
Prefer the fast types by default.
Use exact-width types only when exact size is part of the contract, such as file formats, protocols, bit layouts, or ABI boundaries.
Use minimum-width types when a lower bound matters but exact width does not and low memory consumption is a priority.
Prefer intCount for counts, sizes, offsets, and indices in project code.
Avoid plain int, long, unsigned, and similar built-in types unless interfacing with APIs that require them.
7. Enum and Option Macros
Prefer the project enum macros when defining enums and option sets.
Use defEnum( name, type ) for closed enums.
Use defOpenEnum( name, type ) only when additional external values are part of the design.
Use defOptions( name, type ) or defClosedOptions( name, type ) for bitmask option sets.
Keep the normal enum naming rules from the standard guide.
Examples:
defEnum( TokenKind, int8 ) {
TokenKindWord_TokenKind = 1,
TokenKindNumber_TokenKind,
};
defOptions( FileFlags, int32e ) {
FileFlagsReadable_FileFlags = 1 << 0,
FileFlagsWritable_FileFlags = 1 << 1,
};
8. Pointer and Nullability Macros
Prefer the project pointer macros in declarations when they make nullability and ownership intent clearer.
Use Opt(type) for nullable values.
Use OutPtr(type), OutPtrOpt(type), OptOutPtr(type), and OptOutPtrOpt(type) for output pointer parameters.
Use PtrArrayOf(type) and its optional variants for pointer arrays.
Prefer these forms over manually spelling _Nullable and _Nonnull.
When the Opt(...) or OutPtr...(...) forms cannot be used, use _opt and _req directly.
_opt and _req bind to the pointer level they follow. There is no space before them: *_opt, not * _opt.
_opt and _req stay closer to * than qualifiers such as const, so write char *_opt const ptr, not char * const _opt ptr.
Continue to follow the normal pointer spacing rules from the standard guide.
Examples:
Opt(char *) FindName( const struct Table * table, intCount index );
bool ReadValue( const char * key, OutPtr(int32e) value );
char *_opt const * _req values;
9. Preprocessor Helper Macros
The wrapper layer also provides common preprocessor helpers. Use them instead of local ad-hoc copies.
Use STR(...) for stringification.
Use CONCAT(...) for token concatenation.
Use COUNT_ARGS(...) for variadic macro dispatch.
These helpers are especially useful when building macro families such as require, assert, guard, and similar wrappers.
10. Other Helpers
The wrapper layer also provides a few common C helpers. Prefer them when they fit the job and improve consistency.
Use nothing for an explicit no-op expression where this reads better than (void)0.
Use nil instead of NULL.
Use static_assert(...) for compile-time checks.
Use likely_true(...) and likely_false(...) only when branch prediction hints are justified.
Prefer shared helpers such as MIN, MAX, CLAMP, ROTL, ROTR, and the HOST_TO_BE* / HOST_TO_LE* macros instead of ad-hoc redefinitions.
Use public only for symbols that really need default visibility outside the current binary or library boundary.
11. Preference Rule
When standard C and the project macro layer offer equivalent ways to express the same thing, prefer the project way in this project.
That applies especially to:
- file prologue wrappers
def/setreturn_unless,continue_unless,break_unless,guardrequire,requireFail,assert,assertFail- integer aliases
- enum and option macros
- pointer and nullability macros
- preprocessor helpers
As always, consistency and readability still win.