Red Programming Language: Static linking support
-->
Pages
Home
About
Getting Started
Download
Documentation
Contributions
Roadmap
Privacy Policy
June 29, 2026
Static linking support
We must free ourselves of the hope that the sea will ever rest.<br>We must<br>learn to sail in high winds.<br>-Aristotle Onassis
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<br>to support static linking of libraries written in C, allowing you<br>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<br>each platform's object format, pull in just the pieces it needs, fold<br>duplicated sections, resolve system symbols and patch relocations by hand, so<br>there was quite a bit of machinery to put together. The reward is the result<br>everyone wants: a single, self-contained binary, with nothing to install<br>beside it.
A simple example
Let's compress some data without shipping a compression library next to our<br>program. For something concrete we will use<br>miniz, a<br>small, MIT-licensed library that implements the well-known zlib and deflate<br>APIs. It is distributed as a single `miniz.c` / `miniz.h` pair, which makes it<br>especially convenient to compile and link.
The first step is to compile miniz into a static library. There are only two<br>things to keep in mind. Red/System currently produces 32-bit code, so the<br>object has to be 32-bit too; and it helps to switch off a couple of compiler<br>extras (C++ exception tables and stack canaries) that would otherwise make the<br>object reference runtime helpers we do not need. On Windows, with MSVC:
cl /c /MT /GS- /EHs-c- /GR- miniz.c<br>lib /out:miniz.lib miniz.obj
Now we map the two functions we need. Notice that the imported name has no<br>extension at all, just `miniz`:
Red/System [Title: "miniz round-trip]
#import [<br>"miniz" cdecl [<br>compress: "mz_compress" [<br>dst [byte-ptr!]<br>dst-len [int-ptr!]<br>src [byte-ptr!]<br>src-len [integer!]<br>return: [integer!]<br>uncompress: "mz_uncompress" [<br>dst [byte-ptr!]<br>dst-len [int-ptr!]<br>src [byte-ptr!]<br>src-len [integer!]<br>return: [integer!]
That extension-less name is where it gets interesting: the toolchain resolves<br>it for you, and a single command-line switch decides how. By default, `miniz`<br>resolves to the shared library for the platform (`miniz.dll` on Windows,<br>`libminiz.so` on Linux) so the program links dynamically, exactly as<br>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<br>`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<br>itself. There is no `miniz.dll` to ship next to it, no `PATH` to set up, no<br>installer to write, just a single, self-contained binary (under 50 KB here)<br>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<br>the extension yourself, in which case the toolchain honors it exactly, and<br>`-s` has no effect on that particular import. This is handy when one program<br>mixes both kinds of linking: a system library you always want dynamic,<br>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<br>whole program at once, or explicit extensions for per-library control.<br>Existing code, which already spells out its `.dll` / `.so` / `.dylib`, keeps<br>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`)<br>Linux (x86) — ELF objects and archives (`.o` / `.a`)<br>Linux ARM , including the Raspberry Pi — ELF ARM, in both ARM and Thumb-2<br>code
macOS (Intel) — Mach-O objects and archives
Cross-compilation works as usual, so `-t RPi -s` builds a self-contained ARM<br>binary right on your desktop, ready to copy over to a Pi and run.<br>A real world case: CherryTracker
I wanted to have a real-world case to test and demo our...