You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
capnproto/kjdoc/index.md

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 Cap'n Proto, 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 Promise API inspired by E (which also inspired JavaScript's Promise).
  • 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 Cap'n Proto), 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 Cap'n Proto -- see installing Cap'n Proto. KJ is built as a separate set of libraries, so that you can link against it without Cap'n Proto 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.