contents

Architecture: zero linked C#

How a compositor works with no C in the link. This page is a tour, not a spec; the source is the spec.

The claim, precisely#

carrot links zero C libraries. The binary is a static PIE with no PT_INTERP, so there is no dynamic linker to ask for anything, and every layer a compositor normally borrows from a C library is implemented in Rust inside the binary:

Normally borrowedcarrot grew
glibc + ld.sotaproot, a maintained pure-Rust libc, under a static PIE that applies its own relocations
libwaylanda declarative wire macro; every opcode pinned by golden byte-fixture tests
libdrm + libgbmraw kernel modesetting ioctls; scanout buffers allocated from Vulkan
libinputevdev decoded by hand off io_uring, accel curves and hwdb DPI included
libxkbcommonpure-Rust keymap compilation, with a full US keymap baked into the binary
libseata from-scratch logind D-Bus client
libdbusits own wire codec, client and server roles
libxcbxparsnip, a generated X11 wire library, plus the window manager speaking it
libpipewirea native client and SPA-POD codec for screencasts
libeian EIS input-injection server
libvulkandlopen-rs, a pure-Rust dynamic linker that loads the GPU driver directly

The deepest trick#

The GPU driver is C and needs a libc. So the binary keeps its dynamic symbol table alive (--export-dynamic) and exports its own Rust libc into it. When dlopen-rs loads Mesa's driver, the driver's NEEDED libc.so.6 resolves against the compositor itself, staged as taproot's libc.so.6. The driver spends its whole life convinced it is talking to glibc.

The runtime#

There is no tokio and no event-loop library. carrot runs a single-threaded async runtime of its own: an io_uring proactor built on raw ring mmaps, a task engine whose scheduler phases mirror the compositor pipeline (events, then layout, then present), and a timer wheel. The whole process parks in one io_uring_enter syscall until the kernel has something to say. Blocking work goes to a second copy of the same runtime on a worker thread.

It compiles with panic = "abort" deliberately: the kernel writes through pointers into ring-owned buffers, and aborting beats a use-after-free during unwinding.

Codegen with receipts#

Three standalone generators produce committed source: the SPIR-V shaders (assembled instruction by instruction; there is no shader language in the repo), the X11 wire codecs, and the embedded keymap. Each embeds a hash of its own generator, and a test fails if the committed output drifts from the code that made it.

What this buys#

One language and one debugger from the top of the window stack down to the syscall boundary, and a build with no C toolchain anywhere in it. The binary's behavior is pinned by its own tests rather than by whichever library versions the distro ships. The tradeoff is that everything has to be built by hand, which is why the ledger of what exists is public.

For the story with more details, dig through the homepage.