docs/reference/kj-style.md
This document provides a style guide for C++ code in the workerd project, based on the KJ style used in the Cap'n Proto project. It covers naming conventions, type usage, memory management, error handling, inheritance, constness, formatting, and other idioms. The guide emphasizes consistency and readability while allowing pragmatic exceptions when justified.
The KJ style guide is self-described as suggestions, not rules. Consistency matters, but pragmatic exceptions are fine.
These guidelines are split across multiple files for context efficiency.
You MUST read the relevant reference files before writing or reviewing code that touches their subject matter. Do not rely on memory or general knowledge — the reference files contain project-specific patterns and idioms that override general C++ conventions. Skipping a relevant reference file WILL lead to incorrect suggestions.
detail/review-checklist.md must be used when performing ANY code review of workerd C++ codedetail/api-patterns.md must be used when code uses or should use kj::Maybe, kj::OneOf,
kj::str(), KJ_DEFER, KJ_SYSCALL, kj::downcast, or KJ iteration helpersdetail/async-patterns.md must be used when code involves kj::Promise, .then(),
.attach(), .eagerlyEvaluate(), coroutines, kj::MutexGuarded, or kj::TaskSetdetail/type-design.md must be used when designing new classes, reviewing class hierarchies,
analyzing constness or thread safety semantics, or deciding value-type vs resource-typeWhen in doubt about whether a reference file is relevant, load it.
The project uses KJ types instead of the C++ standard library:
| Instead of | Use |
|---|---|
std::string | kj::String (owned) / kj::StringPtr (borrowed) |
std::vector | kj::Array<T> (fixed) / kj::Vector<T> (growable) |
std::unique_ptr | kj::Own<T> |
std::shared_ptr | kj::Rc<T> / kj::Arc<T> (thread-safe) |
std::optional | kj::Maybe<T> |
std::function | kj::Function<T> |
std::variant | kj::OneOf<T...> |
std::span / array ref | kj::ArrayPtr<T> |
std::exception | kj::Exception |
std::promise/future | kj::Promise<T> / kj::ForkedPromise<T> |
T* (nullable) | kj::Maybe<T&> |
<string>, <vector>, <memory>, <optional>, <functional>,
<variant>, or other std headers from header files. Source-file-only use of std:: is acceptable
when KJ has no equivalent or when use is required when interacting with a dependency.FILE*, iostream, or C stdio.kj::str() for formatting, KJ_DBG() for debug printing, and kj/io.h for I/O.KJ_DBG statements in committed code.Never write new or delete. Use:
kj::heap<T>(args...) → returns kj::Own<T>kj::heapArray<T>(size) → returns kj::Array<T>kj::heapArrayBuilder<T>(size) → build arrays element-by-elementkj::rc<T>(args...) → returns kj::Rc<T>kj::arc<T>(args...) → returns kj::Arc<T>Always use heap alocations when you need moveability.
Always prefer stack or member variables when possible and moving is not needed.
Always prefer an expicit clone() method over copy constructors or copy assignment
kj::Own<T> is an owned pointer that transfers ownership when movedReference counting is allowed. If used:
kj::AtomicRefcounted) is extremely slowkj::AtomicRefcounted is only appropriate for objects that whose refcounting needs to occur
across threads.kj::Rc<T>/kj::rc<T>(...) and kj::Arc<T>/kj::arc<T>(...)
instead of kj::refcounted<T>, kj::atomicRefcounted<T>, and kj::addRef() directly.Never use throw directly. Use KJ assertion macros from kj/debug.h:
KJ_ASSERT(cond, msg, values...) — checks invariants (bug in this code)KJ_REQUIRE(cond, msg, values...) — checks preconditions (bug in caller)KJ_FAIL_ASSERT(msg, values...) — unconditional failureKJ_SYSCALL(expr, msg, values...) — wraps C syscalls, checks return valuesKJ_UNREACHABLE — marks unreachable codekj::throwFatalError(msg, values...) — throws an exception with a stack traceThese macros automatically capture file/line, stringify operands, and generate stack traces.
Never declare anything noexcept. Bugs can happen anywhere; aborting is never correct.
Always ensure that explicit destructors use noexcept(false)**. Use kj::UnwindDetector or
recovery blocks in destructors to handle the unwinding-during-unwind problem.
Exceptions are for fault tolerance, not control flow. They represent things that "should never
happen" — bugs, network failures, resource exhaustion. If callers need to catch an exception
to operate correctly, the interface is wrong. Offer alternatives like openIfExists() returning
kj::Maybe.
Always prefer RAII for resource management.
Cleanup should happen in destructors. A destructor should perform no more than one cleanup action.
Use KJ_DEFER(code) for scope-exit cleanup.
| Kind | Style |
|---|---|
| Types (classes, structs) | TitleCase |
| Variables, functions, methods | camelCase |
| Constants, enumerants | CAPITAL_WITH_UNDERSCORES |
| Macros | CAPITAL_WITH_UNDERSCORES with project prefix (KJ_, CAPNP_) |
| Namespaces | oneword (keep short); private namespace: _ |
| Files | module-name.c++, module-name.h, module-name-test.c++ |
[=] (capture-all-by-value) — makes lifetime analysis impossible during review[&] (capture-all-by-reference) but only when the lambda will not outlive the
current stack frame. Using [&] signals "this lambda doesn't escape."kj::coCapture(...) to capture variables in a way that extends their lifetime for the duration of the promise.Never use mutable globals or global registries. The main() function or high-level code should
explicitly construct components and wire dependencies via constructor parameters.
Always validate data at time-of-use, not upfront. Upfront validation creates false confidence downstream, gets out of sync with usage code, and wastes cycles on unused fields.
All text is UTF-8.
Never assume text is valid UTF-8. Be tolerant of malformed sequences.
if (foo), for (...), while (...)foo(bar)public:/private:/protected: reverse-indented by one stop from class bodyWorkerd follows these convention over KJ's own comment style:
// line comments. Never /* */ block comments// TODO(type): description where type is: now, soon, someday, perf,
security, cleanup, port, testTODO(now) comments must be addressed before merging.TODO({project}) type comments where {project} is the name of an active project
for work that is carried out over multiple pull requests.