Red Programming Language: Static linking support

em-bee1 pts0 comments

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...

miniz static library always linking single

Related Articles