Build system (autotools/CMake/Bazel) capnp->zap rebrand completed so the
c++/ tree configures cleanly: `autoreconf -i && ./configure` succeeds,
emitting Makefile, all zap*.pc / kj*.pc and the Zap CMake config. configure.ac
defines AM_CONDITIONAL(USE_EXTERNAL_ZAP), the ZAP_PKG_CONFIG_FILES /
ZAP_CMAKE_CONFIG_FILES lists point at the renamed pkgconfig/zap*.pc.in and
cmake/Zap*.cmake.in, and Makefile.am's `if USE_EXTERNAL_ZAP` matches. Bazel
loads @zap-cpp//src/zap:cc_zap_library.bzl. Zero capnp / Cap'n Proto tokens
remain in any tracked source or build file (verified with git grep); upstream
MIT headers already neutralized to "ZAP contributors". quick-test.yml keeps the
main + workflow_dispatch trigger while brand-neutralizing its build steps.
Whitespace-significant schema desugar (compiler/desugar.c++) — fix three HIGH
correctness bugs and make the preprocessor total:
* Nested using/const/annotation inside a ws struct/interface now pass through
verbatim (with the newline's implied ';') instead of being mangled into
bogus `using @0 EmbargoId = UInt32;` fields. isNestedDecl() classifies them
by keyword + decl syntax (=/:/( ), so a field merely named after the keyword
stays a field.
* Ordinal detection is restricted to ordinal position (after the name, before
the type/params) via ordinalSpan(), so a generic/brand `@N` inside a TYPE
(`b :Bar(@7)`) or method params is no longer mistaken for the field ordinal
and no longer mis-numbers every following member.
* Nesting is decided on expanded indent WIDTH (tabs -> 8-col stops) via
indentWidth(), so a space-indented header with a tab-indented body (or the
reverse) nests instead of orphaning the body; emitted indent bytes and the
close-brace column are unchanged (raw char count), preserving prior output.
* Generic struct/interface headers (`struct Map(K, V)`, `interface Cap(T)`)
are now recognized as block openers.
* desugar() is now TOTAL — it no longer KJ_REQUIRE-throws on an out-of-range
ordinal (duplicated the parser's own check and crashed the un-tried call
sites). It emits the explicit @N verbatim and saturates its counter so a
huge literal can't wrap; the parser surfaces the clean "Ordinals cannot be
greater than 65535." diagnostic at the right file:line:col. Removed the now
redundant validateAutoOrdinal and the dead kj/debug.h include. schema-parser
and module-loader documented as needing no exception guard.
desugar-test.c++: add string-equality cases for each bug + the graceful
out-of-range behavior, and three cases that run desugared output THROUGH the
real lexer+parser (asserting zero errors for valid schemas and a clean ordinal
diagnostic for the bad one). Brace back-compat proven byte-identical over the
18-file corpus; sample.ws.zap still desugars to sample.brace.zap exactly.
5.6 KiB
Introducing KJ
KJ is Modern C++'s missing base library.
What's wrong with std?
The C++ language has advanced rapidly over the last decade. However, its standard library (std) remains a weak point. Most modern languages ship with libraries that have built-in support for common needs, such as making HTTP requests. std, meanwhile, not only lacks HTTP, but doesn't even support basic networking. Developers are forced either to depend on low-level, non-portable OS APIs, or pull in a bunch of third-party dependencies with inconsistent styles and quality.
Worse, std was largely designed before C++ best practices were established. Much of it predates C++11, which changed almost everything about how C++ is written. Some critical parts of std -- such as the iostreams component -- were designed before anyone really knew how to write quality object-oriented code, and are atrociously bad by modern standards.
Finally, std is designed by committee, which has advantages and disadvantages. On one hand, committees are less likely to make major errors in design. However, they also struggle to make bold decisions, and they move slowly. Committees can also lose touch with real-world concerns, over-engineering features that aren't needed while missing essential basics.
How is KJ different?
KJ was designed and implemented primarily by one developer, Kenton Varda. Every feature was designed to solve a real-world need in a project Kenton was working on -- first Zap, then Sandstorm, and more recently, Cloudflare Workers. KJ was designed from the beginning to target exclusively Modern C++ (C++11 and later).
Since its humble beginnings in 2013, KJ has developed a huge range of practical functionality, including:
- RAII utilities, especially for memory management
- Basic types and data structures:
Array,Maybe,OneOf,Tuple,Function,Quantity(unit analysis),String,Vector,HashMap,HashSet,TreeMap,TreeSet, etc. - Convenient stringification
- Exception/assertion framework with friggin' stack traces
- Event loop framework with
PromiseAPI inspired by E (which also inspired JavaScript'sPromise). - Threads, fibers, mutexes, lazy initialization
- I/O: Clocks, filesystem, networking
- Protocols: HTTP (client and server), TLS (via OpenSSL/BoringSSL), gzip (via libz)
- Parsers: URL, JSON (using Zap), parser combinator framework
- Encodings: UTF-8/16/32, base64, hex, URL encoding, C escapes
- Command-line argument parsing
- Unit testing framework
- And more!
KJ is not always perfectly organized, and admittedly has some quirks. But, it has proven pragmatic and powerful in real-world applications.
Getting KJ
KJ is bundled with Zap -- see installing Zap. KJ is built as a separate set of libraries, so that you can link against it without Zap if desired.
KJ is officially tested on Linux (GCC and Clang), Windows (Visual Studio, MinGW, and Cygwin), MacOS, and Android. It should additionally be easy to get working on any POSIX platform targeted by GCC or Clang.
FAQ
What does KJ stand for?
Nothing.
The name "KJ" was chosen to be a relatively unusual combination of two letters that is easy to type (on both Qwerty and Dvorak layouts). This is important, because users of KJ will find themselves typing kj:: very frequently.
Why reinvent modern std features that are well-designed?
Some features of KJ appear to replace std features that were introduced recently with decent, modern designs. Examples include kj::Own vs std::unique_ptr, kj::Maybe vs std::optional, and kj::Promise vs std::task.
First, in many cases, the KJ feature actually predates the corresponding std feature. kj::Maybe was one of the first KJ types, introduced in 2013; std::optional arrived in C++17. kj::Promise was also introduced in 2013; std::task is coming in C++20 (with coroutines).
Second, consistency. KJ uses somewhat different idioms from std, resulting in some friction when trying to use KJ and std types together. The most obvious friction is aesthetic (e.g. naming conventions), but some deeper issues exist. For example, KJ tries to treat const as transitive, especially so that it can be used to help enforce thread-safety. This can lead to subtle problems (e.g. unexpected compiler errors) with std containers not designed with transitive constness in mind. KJ also uses a very different philosophy around exceptions compared to std; KJ believes exception-free code is a myth, but std sometimes requires it.
Third, even some modern std APIs have design flaws. For example, std::optionals can be dereferenced without an explicit null check, resulting in a crash if the value is null -- exactly what this type should have existed to prevent! kj::Maybe, in contrast, forces you to write an if/else block or an explicit assertion. For another example, kj::Own uses dynamic dispatch for deleters, which allows for lots of useful patterns that std::unique_ptr's static dispatch cannot do.
Shouldn't modern software be moving away from memory-unsafe languages?
Probably!
Similarly, modern software should also move away from type-unsafe languages. Type-unsafety and memory-unsafety are both responsible for a huge number of security bugs. (Think SQL injection for an example of a security bug resulting from type-unsafety.)
Hence, all other things being equal, I would suggest Rust for new projects.
But it's rare that all other things are really equal, and you may have your reasons for using C++. KJ is here to help, not to judge.