This document defines the coding conventions for this codebase. Follow these rules in all new code and when modifying existing code. The rules are mandatory unless a specific situation makes them impossible, in which case prefer the spirit of the rule and keep the deviation local and obvious.
- Indent with tabs only, one tab per nesting level. Never use spaces to produce indentation.
- Use spaces only for table-like alignment after the first non-whitespace character on a line (for example, lining up the columns of a multi-line expression). Indentation is tabs; alignment is spaces.
- The tab indent level of a line may be at most one greater than the indent level of the previous line. Do not skip levels (for example, jumping from one tab to three) to align with an unrelated construct.
-
Place the opening brace of a function or method definition on its own line:
void WlResource::Destroy() { wl_resource_destroy(ToResource()); }
-
Place the opening brace of a class, struct, enum, namespace, and control-flow statement (
if,else,for,while,switch) on the same line:class HaikuCompositor: public WlCompositor { public: void HandleCreateSurface(uint32_t id) override; }; if (resource == nullptr) { return nullptr; }
-
A trivial inline body of a function or method may stay on a single line with the braces:
struct wl_resource *ToResource() const {return fResource;} void Unset() {fPtr = nullptr;}
-
The body of every
if,else,for,while,do, andswitchmust be enclosed in{}, even when it is a single statement. There are no brace-less control-flow bodies.if (!global.IsSet()) { return nullptr; } if (fPtr != nullptr) { fPtr->LockLooper(); }
- One space after control-flow keywords:
if (,for (,while (,switch (. - No space between a function name and its parenthesis:
Init(,Create(. - Bind the pointer/reference marker to the variable name, not the type:
struct wl_resource *fResource,HaikuSurface *surface,BPoint &pt.
-
An opening delimiter (
(,{,[) and its matching closing delimiter must sit at the same indent level when the construct spans multiple lines. The closing delimiter lines up under the start of the line that opened it, not under the content:fPendingState.viewportSrc = { x, y, width, height }; window->SetSizeLimits( minWidth, maxWidth, minHeight, maxHeight );
-
Do not mechanically wrap a long line at an arbitrary column. Wrapping must follow the structure of the code.
-
When an argument list, array initializer, or similar comma-separated construct does not fit on one line, switch to one item per line:
HaikuSurface *Create( struct wl_client *client, uint32_t version, uint32_t id );
-
As an exception, semantically related items may share a line even in one-item-per-line layout — for example paired coordinates or dimensions:
BRect rect = { left, top, right, bottom };
-
Separate top-level definitions with two blank lines. Separate logical groups of members or statements within a body with a single blank line.
-
Leave two blank lines after the include section, before the first declaration or definition.
-
Two blank lines between code blocks may be used as a lightweight section separator.
-
Precede a
//#pragma mark -section banner (see section 9) with two blank lines and follow it with one blank line:} //#pragma mark - HaikuSurface HaikuSurface::~HaikuSurface() { ... }
- Use LF line endings.
- Trim trailing whitespace from every line.
- End every non-empty source file with exactly one trailing newline. An empty file stays empty.
- Name classes, structs, and enums in PascalCase:
HaikuSurface,FrameCallback,ViewportSrc. - Prefix a class that wraps a protocol/global object with its domain prefix
consistently (for example
Haiku*for platform-backed implementations,Wl*for protocol base wrappers). Keep the prefix uniform across the layer.
- Name functions and methods in PascalCase:
Dispatch,FromResource,AttachWindow. - Name a method that handles an incoming protocol request
Handle<Request>:HandleCommit,HandleCreateSurface,HandleSetOpaqueRegion. - Name a method that emits an outgoing event
Send<Event>:SendDone,SendConfigure,SendRelease. - Name a static downcast/lookup helper
FromResource(orFrom<Source>), and a static factoryCreate.
-
Prefix non-public data members with
f:fResource,fState,fSurface. -
Prefix mutable global variables with
g:gServerHandler,gServerMessenger. -
Prefix file-local
staticvariables withs:static int32_t sStaticGlobalVar = 123;
-
Name local variables in camelCase:
viewLocked,pendingState,envValue. -
Name enumerators of an unscoped
enumin PascalCase with a shared semantic prefix that groups them, rather thanALL_CAPS:enum StateField { FieldBuffer, FieldOffset, FieldTransform, };In a scoped
enum class(preferred — see section 10) the enum name already scopes the members, so the prefix is unnecessary:Mode::None,Mode::Server.
-
Name constants — both global and
static/const/constexprclass data members — in camelCase with akprefix:constexpr int32_t kSomeConst = 123;
-
Reserve
ALL_CAPSfor macros (including include guards). Prefer a typedconstexprconstant over a#definewhenever a macro is not actually required.
- Start every project header with
#pragma once. Use traditional#ifndef/#defineinclude guards only when a file must remain portable to environments without#pragma oncesupport. - Order includes in the following groups, separated by a single blank line:
- The corresponding header for this source file (in a
.cpp). - Standard C library headers (
<stdio.h>,<stdlib.h>, ...). - Standard C++ library headers (
<optional>,<utility>, ...). - Global
<>headers (framework and third-party). - Local
""headers (project headers).
- The corresponding header for this source file (in a
- Use forward declarations in headers instead of
#includewhenever only a pointer or reference to a type is needed. Include the full definition only in the.cpp(or where the layout is required). - Keep one primary class per header/source pair, named after the class. Small
private helper classes local to one translation unit may live inside the
.cpp.
-
When a module's declarations are wrapped in a
namespace, the opening brace goes on the same line as thenamespacekeyword (section 1.2); close it with a trailing comment naming the namespace. Leave two blank lines after the opening brace and two blank lines before the closing brace, matching the file-level spacing of section 1.7:#include <stdio.h> namespace SomeNamespace { void Do1(); void Do2(); } // SomeNamespace
-
Do not introduce names at global (file) scope with a
usingdirective (using namespace Foo;) or ausingdeclaration (using Foo::Bar;), in either a header or a source file — it pollutes every translation unit that sees the name. Confine suchusingstatements to the narrowest scope that needs them (a function body, or a namespace block). This restriction does not apply tousingtype aliases (section 10), which declare a new name rather than import an existing one.
-
Do fallible initialization in the constructor and report failure with an exception (see section 8). A fully constructed object is then always valid, so callers need no separate "is it ready?" check.
-
Format the member initializer list with the colon directly after the closing parenthesis of the signature, then one initializer per line indented one level, and the opening brace on its own line (per section 1.2). List the base class(es) first, then members in declaration order — the order they are actually initialized:
Object::Object(int32_t a, int32_t b, int32_t c): Base(a), fB(b), fC(c) { // ... }
-
Initialize members at the point of declaration. Use
{}for value-initialization and= nullptrfor pointers that need an explicit null:struct wl_resource *fResource = nullptr; uint32 fPendingFields {}; WaylandView *fView {};
-
Declare
privatemembers first, thenpublic, unless readability strongly favors otherwise. Group related members together. -
Grant access to collaborating classes with explicit
frienddeclarations rather than widening visibility or adding pass-through accessors.
- Declare base-class polymorphic methods
virtualand give every base class avirtualdestructor (virtual ~T() = default;when trivial). - Mark an override that is meant to be further overridden with
override, and one that must not be overridden again withfinal. Always use one or the other on an overriding method. - Express an abstract contract with pure virtual methods (
= 0).
-
Write getters as short inline
constmethods named after the property, returning the stored value directly:uint32_t Id() const {return wl_resource_get_id(fResource);} HaikuXdgSurface *XdgSurface() {return fXdgSurface;}
-
Const correctness is expected, not optional. Apply
consteverywhere it holds:- Mark a method
constwhen it does not modify the observable state of its object — every accessor in section 4.4, and any other query method. - Take a parameter by
constreference orconstpointer when the callee only reads it (the borrow case in section 6.1). - Qualify a local or member with
constwhen it is initialized once and never reassigned.
- Mark a method
-
Use RAII wrappers for every resource that must be released: scope-bound smart/owner types (
ObjectDeleter,AreaDeleter,FileDescriptorCloser) for ownership, and reference-counted base types (BReferenceable/BReference) for shared ownership. -
Use scope-guard objects for paired acquire/release operations (locks, contexts) so the release happens automatically on every exit path:
auto viewLocked = AppKitPtrs::LockedPtr(this); // unlocked automatically at end of scope
-
Transfer ownership out of an owner wrapper explicitly with
Detach(); query it withIsSet(). Do not bypass the wrapper to access the raw resource while it owns it. -
Tie an object's lifetime to its protocol resource where that is the model: handle destruction through the resource's destructor callback, and let last-reference release trigger teardown for reference-counted objects.
- Use
nullptrfor null pointers — neverNULLor0. - Compare pointers explicitly against
nullptr: writeif (ptr != nullptr)andif (ptr == nullptr), notif (ptr)orif (!ptr). (A wrapper object that definesoperator bool/IsSet()is tested with that member instead.) - Guard every pointer dereference that can legitimately be null, and return early on the null case.
The choice between a reference and a pointer parameter carries meaning about lifetime and ownership; pick the one that states the intent.
- Pass by reference (
T&,const T&) to express borrow semantics: the callee may read or modify the object only for the duration of the call. It must not store the address or use it after returning. Prefer a reference wherever only borrowing is intended. - Pass by pointer when the address legitimately outlives the call — the callee may register the pointer in its own internal state, or the call may transfer ownership of the pointee. A pointer parameter is therefore a signal that "this address may be kept or handed over", which a reference would not imply.
- For ownership transfer, prefer moving an RAII owner wrapper (section 5) over a raw pointer, so the hand-off is explicit and leak-safe.
-
Prefer early returns (guard clauses) over deep nesting. Validate inputs and bail out at the top of the function:
void HaikuSurface::Detach() { if (fView == nullptr) { return; } // ... main body, not nested ... }
-
Use
switchfor dispatch over an enum or opcode. Keep case bodies short, delegating to helpers when they grow. -
Indent
caselabels one level deeper than theswitchkeyword. -
End every branch with
break, including thedefaultbranch. -
When a
casedeclares its own local variables, wrap its body in{}, with the opening brace on the same line as thecaselabel:switch (field) { case FieldOffset: { int32_t dx = NextX(); ApplyOffset(dx); break; } case FieldScale: ApplyScale(); break; default: break; }
-
Several
caselabels stacked with no code between them (only the last carries the body) read as "any of these options", not as fall-through, and need no comment:switch (colorSpace) { case B_RGBA32: case B_RGBA64: case B_RGBA15: mode = B_OP_ALPHA; break; default: mode = B_OP_COPY; break; }
-
Genuine fall-through — a
casewith code that then continues into the next — is rare. Where it is truly needed, add a comment explaining why. Likewise, sharing locals across cases (the unusual reason to omit per-case braces) should be explained.
- Prefer exceptions over error codes for reporting failure. A function that cannot complete throws; callers that cannot proceed let the exception propagate. This keeps the success path free of error-checking noise.
-
When an external (typically C) library returns an error code, wrap it in a
std::system_errorat the boundary, so the rest of the code deals only in exceptions. Use a small helper to convert-and-throw:void CheckThrow(status_t res) { if (res < B_OK) { throw std::system_error(res, std::generic_category()); } } void Do() { CheckThrow(LibCall1()); CheckThrow(LibCall2()); }
-
Where it is genuinely necessary to keep handling raw error codes (a tight boundary, performance-critical path, or code that must stay code-based), use a check-and-early-return macro rather than open-coding the test at every call:
#define CHECK_RET(err) {status_t _err = (err); if (_err < B_OK) return _err;} status_t Do() { CHECK_RET(LibCall1()); CHECK_RET(LibCall2()); return B_OK; }
-
When a callback invoked by an external library must return an error code (and must not let an exception escape across the C boundary), catch everything at the boundary and convert the exception back into a code:
status_t Callback() { try { DoWork(); return B_OK; } catch (const std::system_error &e) { return e.code().value(); } catch (...) { return B_ERROR; } }
- Use an assertion helper for invariants that must hold (
Assert(cond)that callsabort()), and reserve the debugger trap for programming errors that indicate a corrupt state. - Report unexpected-but-recoverable conditions to
stderrwith a recognizable prefix when an exception is not appropriate (for example deep inside a callback). - Remove dead debug output before committing.
-
Mark major sections within a translation unit with a banner comment so the file reads as a sequence of clearly delimited components:
//#pragma mark - HaikuSurface -
Prefix actionable follow-ups with
TODO:and state what needs to happen. -
Write comments that explain why, not what the code obviously does. Keep them current with the code they describe.
-
Write code as if it were written from scratch in its final form. Do not leave comments that describe what changed, what the code used to be, or why an edit was made (
// was 5, now 10,// removed old loop,// fixed bug). That history belongs in Git commit messages, not in the source.
-
Target modern C++ and use the standard library where it fits:
std::optionalfor maybe-present values,std::moveto transfer ownership,std::min/std::max, and<bit>utilities for bit manipulation. -
Prefer
enum class(scoped, strongly typed) over a plainenum. Use an unscopedenumonly where implicit conversion to the underlying integer is genuinely required (for example a bit-flag field built with|). -
Use
autowhen the initializer makes the type obvious (especially for verbose wrapper types), and spell the type out when it aids comprehension. -
Use a C++ named cast (
static_cast,reinterpret_cast,const_cast) rather than a C-style cast, so the kind and intent of the conversion is explicit and greppable:auto *surface = static_cast<HaikuSurface*>(wl_resource_get_user_data(resource)); auto *pixels = reinterpret_cast<uint8_t*>(data);
-
Declare type aliases with
using, nottypedef— it reads left-to-right and works with templates:using SurfaceList = DoublyLinkedList<HaikuSubsurface, SubsurfaceLink>;