We must free ourselves of the hope that the sea will ever rest.
The coding agents revolution is taking the world by storm and we are right in the middle of it. Like most of you, we have experimented with the agent's amazing (and frustrating) capabilities, pondering the role of Red and our vision in that new world. The conclusion is (un)surprisingly clear, Red is still very relevant and will be even more so as we improve it to better work with agents.
In the meantime, here are some treats, starting with expanding our toolchain to support static linking of libraries written in C, allowing you to distribute single executables with all dependencies packed inside. This work has been done with the heavy assistance of frontier models and local harnesses (Claude Code and Codex).
You might expect that to be a small addition, but a static linker has to read each platform's object format, pull in just the pieces it needs, fold duplicated sections, resolve system symbols and patch relocations by hand, so there was quite a bit of machinery to put together. The reward is the result everyone wants: a single, self-contained binary, with nothing to install beside it.
A simple example
Let's compress some data without shipping a compression library next to our program. For something concrete we will use miniz, a small, MIT-licensed library that implements the well-known zlib and deflate APIs. It is distributed as a single `miniz.c` / `miniz.h` pair, which makes it especially convenient to compile and link.
The first step is to compile miniz into a static library. There are only two things to keep in mind. Red/System currently produces 32-bit code, so the object has to be 32-bit too; and it helps to switch off a couple of compiler extras (C++ exception tables and stack canaries) that would otherwise make the object reference runtime helpers we do not need. On Windows, with MSVC:
cl /c /MT /GS- /EHs-c- /GR- miniz.c
lib /out:miniz.lib miniz.obj
Now we map the two functions we need. Notice that the imported name has no extension at all, just `miniz`:
Red/System [Title: "miniz round-trip]
#import [
"miniz" cdecl [
compress: "mz_compress" [
dst [byte-ptr!]
dst-len [int-ptr!]
src [byte-ptr!]
src-len [integer!]
return: [integer!]
]
uncompress: "mz_uncompress" [
dst [byte-ptr!]
dst-len [int-ptr!]
src [byte-ptr!]
src-len [integer!]
return: [integer!]
]
]
]
That extension-less name is where it gets interesting: the toolchain resolves it for you, and a single command-line switch decides how. By default, `miniz` resolves to the shared library for the platform (`miniz.dll` on Windows, `libminiz.so` on Linux) so the program links dynamically, exactly as Red/System has always done:
red -r demo.reds ; "miniz" resolves to miniz.dll, linked dynamically
Now add `-s`, for *static* (or the longer `--static`), and the very same `miniz` resolves to the static library we built a moment ago instead:
red -r -s demo.reds ; "miniz" resolves to miniz.lib, linked statically
This time `mz_compress` and `mz_uncompress` become part of the executable itself. There is no `miniz.dll` to ship next to it, no `PATH` to set up, no installer to write, just a single, self-contained binary (under 50 KB here) that you can copy anywhere and run. The same source file, one extra flag.
Being explicit, when you prefer
The extension-less name is convenient, but you are always free to spell out the extension yourself, in which case the toolchain honors it exactly, and `-s` has no effect on that particular import. This is handy when one program mixes both kinds of linking: a system library you always want dynamic, alongside a helper you always want baked in. For example:
#import ["user32.dll" stdcall [...]] ; always dynamic
#import ["miniz.lib" cdecl [...]] ; always static (Windows)
#import ["libminiz.a" cdecl [...]] ; always static (Linux / macOS)
So you get the best of both: an extension-less name plus `-s` to switch a whole program at once, or explicit extensions for per-library control. Existing code, which already spells out its `.dll` / `.so` / `.dylib`, keeps working unchanged. So those additions to the toolchain are backward-compatible with your current codebases.
Supported targets
Static linking works across all of Red's main native targets:
- Windows (x86) — COFF objects and libraries (`.obj` / `.lib`)
- Linux (x86) — ELF objects and archives (`.o` / `.a`)
- Linux ARM, including the Raspberry Pi — ELF ARM, in both ARM and Thumb-2 code
- macOS (Intel) — Mach-O objects and archives
Cross-compilation works as usual, so `-t RPi -s` builds a self-contained ARM binary right on your desktop, ready to copy over to a Pi and run.
A real world case: CherryTracker
32-bit static versions of those libs are provided in the libs folder, for both target OS. The import code is using the extension-less option:
#import ["libs/libxmp" cdecl [...]]
#import ["libs/SDL3" cdecl [...]]
In my local repo, I also placed the shared lib version of those external libraries. Using the dev compilation mode of the Red toolchain (`-c`) and those shared libraries, the agent and I have the ability then to recompile very quickly new versions for testing (using libRedRT), then for release versions, I switch to static linking using `-r -s` compilation options. This approach has proven to be very simple and efficient during the whole work on this application (in my spare time, over a few weeks).
Try it and see what Red is capable of when coupled with coding agents, especially if you are old enough to have been playing mods in the 80s/90s!
A look under the hood
This is a real static linker, not a "concatenate the bytes" trick. It reads the three native object formats, COFF, ELF and Mach-O, through a common interface, and takes care of the parts that make static linking actually work:
➤ Selective archive loading: An archive can hold hundreds of object files; the linker pulls in only the members that resolve a symbol something already references, then follows that dependency chain to its end. Linking against a large `.lib` does *not* give you a large executable.
➤ COMDAT and weak-symbol folding: C++ (and modern C, through `inline`) emits the same template instantiation, inline function or vtable in every translation unit, expecting the linker to keep one copy and discard the rest. The linker tracks those groups and folds the duplicates, along with the relocations that pointed at them.
➤ Full relocation support, per format: Including the awkward ones: the ARM Thumb-2 split immediates and BL/BLX interworking needed for the Pi, and the Mach-O scattered section-difference relocations that optimized switch tables produce.
➤ System symbol resolution: References to libc / libSystem, plus a built-in set of common compiler intrinsics (64-bit division, stack probes and the like), are resolved automatically, so simple programs link without having to drag in the whole C runtime.
In other words, it behaves the way `link.exe`, `ld` and `ld64` do for the subset that Red/System needs, which is exactly what keeps the simple case ("just link this library") simple.
A few things are deliberately left for later. Full C++ runtime support (exceptions, RTTI, the `std::` library) is a much deeper rabbit hole. For now, plain-C APIs (including the C APIs that many C++ libraries expose) are the path to follow. Debug-information passthrough and incremental linking are on the list as well.
Give it a try, and if you run into a library that does not link cleanly, please tell us about it (just open a ticket). We would love to see what you build with it.
Enjoy!

