<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Anton Zhiyanov</title><description>Everything about Go, SQL, and software in general.</description><link>https://antonz.org/</link><image><url>https://antonz.org/assets/favicon/favicon.png</url><title>Anton Zhiyanov</title><link>https://antonz.org/</link></image><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 20 Aug 2026 12:00:00 +0000</lastBuildDate><atom:link href="https://antonz.org/index.xml" rel="self" type="application/rss+xml"/><item><title>Going freestanding</title><link>https://antonz.org/going-freestanding/</link><pubDate>Thu, 20 Aug 2026 12:00:00 +0000</pubDate><guid>https://antonz.org/going-freestanding/</guid><description>Porting Go's standard library to platform-agnostic C.</description><content:encoded><![CDATA[<p>Creating a subset of Go that <a href="/solod/">translates to C</a> (which I named Solod) was never my end goal. I liked writing C code with Go, but without the standard library it felt pretty limited. So the next logical step was to port Go's stdlib.</p>
<p>At some point I decided to make as many packages as possible <em>freestanding</em> — independent of any libc implementation or specific OS runtime. That went pretty well. Solod now has 37 standard library packages, and 31 of them work in freestanding mode.</p>
<p>This post describes the techniques I used to get there. There's nothing genuinely novel, and if you're experienced with C, you probably already know all of them. Still, I think it's useful to document the approach — both for me and for anyone interested.</p>
<p><a href="#freestanding-mode">Freestanding mode</a> •
<a href="#freestanding-headers">Headers</a> •
<a href="#compiler-builtins">Builtins</a> •
<a href="#memory-operations">Memory</a> •
<a href="#atomic-operations">Atomics</a> •
<a href="#pure-c-implementations">Pure C</a> •
<a href="#memory-allocation">Allocation</a> •
<a href="#values-not-pointers">Values</a> •
<a href="#target-hooks">Hooks</a> •
<a href="#hosted-only">Hosted-only</a> •
<a href="#testing">Testing</a> •
<a href="#final-thoughts">Final thoughts</a></p>
<h2 id="freestanding-mode">Freestanding mode</h2>
<p>C has two types of environments. In a <em>hosted</em> environment, you get the full standard library — either the one required by the C standard or, even better, POSIX. In a <em>freestanding</em> environment, you get almost nothing.</p>
<p>The compiler tells you which one you're in:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#if __STDC_HOSTED__
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="c1">// libc is available
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#else
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="c1">// you&#39;re on your own
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#endif
</span></span></span></code></pre></div><p>Pass <code>-ffreestanding</code> and link with <code>-nostdlib</code>, and that's it: you no longer have <code>printf</code>, or <code>malloc</code>, or even <code>memcpy</code>. There is no entropy source, no file system operations, and no clock. If libc itself is &quot;hard mode&quot;, this is &quot;impossible&quot;.</p>
<p>Despite its limitations, freestanding mode can be really useful for microcontrollers, WebAssembly sandboxes, kernels, and anything else without an operating system to rely on.</p>
<h2 id="freestanding-headers">Freestanding headers</h2>
<p>Freestanding does not mean &quot;just the C language&quot;. The C standard guarantees some headers even without libc, because they define types and macros instead of functions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">float.h   stdalign.h  stdbool.h  stdint.h
</span></span><span class="line"><span class="cl">limits.h  stdarg.h    stddef.h   ...
</span></span></code></pre></div><p>Everything that requires actual function implementations is gone:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">assert.h  math.h   stdlib.h  time.h
</span></span><span class="line"><span class="cl">errno.h   stdio.h  string.h  ...
</span></span></code></pre></div><p>To reflect the hosted/freestanding split, let's introduce <code>builtin.h</code>, a common header included in every standard library package:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#if __STDC_HOSTED__
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;assert.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;inttypes.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdalign.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdbool.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;string.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#define so_build_hosted
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#else
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdbool.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdalign.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stddef.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif  </span><span class="c1">// __STDC_HOSTED__
</span></span></span></code></pre></div><p>Individual packages follow the same approach: branch on <code>so_build_hosted</code> to distinguish between the hosted and freestanding implementations.</p>
<h2 id="compiler-builtins">Compiler builtins</h2>
<p>GCC and Clang implement some C standard functions without relying on libc. These are known as compiler builtins.</p>
<p><code>__builtin_trap</code> causes the program to terminate abnormally. You can use it to implement poor man's assertion and panic:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#ifdef so_build_hosted
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#define so_panic(msg)                                     \
</span></span></span><span class="line"><span class="cl"><span class="cp">    do {                                                  \
</span></span></span><span class="line"><span class="cl"><span class="cp">        fprintf(stderr, &#34;panic: %s\n  %s:%d (func %s)\n&#34;, \
</span></span></span><span class="line"><span class="cl"><span class="cp">                msg, __FILE__, __LINE__, __func__);       \
</span></span></span><span class="line"><span class="cl"><span class="cp">        exit(1);                                          \
</span></span></span><span class="line"><span class="cl"><span class="cp">    } while (0)
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#else
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#define assert(cond)                   \
</span></span></span><span class="line"><span class="cl"><span class="cp">    do {                               \
</span></span></span><span class="line"><span class="cl"><span class="cp">        if (!(cond)) __builtin_trap(); \
</span></span></span><span class="line"><span class="cl"><span class="cp">    } while (0)
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#define so_panic(msg)     \
</span></span></span><span class="line"><span class="cl"><span class="cp">    do {                  \
</span></span></span><span class="line"><span class="cl"><span class="cp">        (void)msg;        \
</span></span></span><span class="line"><span class="cl"><span class="cp">        __builtin_trap(); \
</span></span></span><span class="line"><span class="cl"><span class="cp">    } while (0)
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif </span><span class="c1">// so_build_hosted
</span></span></span></code></pre></div><blockquote>
<p>From now on, I'll mainly show the freestanding versions and omit the hosted versions to keep things simple.</p>
</blockquote>
<p>The <code>__builtin_alloca</code> function allocates memory on the stack. Its bounded wrapper limits the size of each allocation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define alloca __builtin_alloca
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// The maximum size that can be allocated
</span></span></span><span class="line"><span class="cl"><span class="c1">// with alloca (64 KB by default).
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#ifndef SO_MAX_ALLOCA_SIZE
</span></span></span><span class="line"><span class="cl"><span class="cp">#define SO_MAX_ALLOCA_SIZE (64 &lt;&lt; 10)  </span><span class="c1">// in bytes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#endif
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#define so_alloca(size) ({                                \
</span></span></span><span class="line"><span class="cl"><span class="cp">    size_t _size = (size_t)(size);                        \
</span></span></span><span class="line"><span class="cl"><span class="cp">    if (_size &gt; SO_MAX_ALLOCA_SIZE)                       \
</span></span></span><span class="line"><span class="cl"><span class="cp">        so_panic(&#34;alloca: size exceeds maximum allowed&#34;); \
</span></span></span><span class="line"><span class="cl"><span class="cp">    _size ? alloca(_size) : NULL;                         \
</span></span></span><span class="line"><span class="cl"><span class="cp">})
</span></span></span></code></pre></div><h2 id="memory-operations">Memory operations</h2>
<p>The <code>memxxx</code> functions from <code>string.h</code> have matching builtins too, so you might expect a freestanding build to provide them for you:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// int memcmp(const void* lhs, const void* rhs, size_t n);
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define memcmp __builtin_memcmp
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// void* memcpy(void* dst, const void* src, size_t n);
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define memcpy __builtin_memcpy
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// void* memmove(void* dst, const void* src, size_t n);
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define memmove __builtin_memmove
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// void* memset(void* dst, int ch, size_t n);
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define memset __builtin_memset
</span></span></span></code></pre></div><p>Unfortunately, there's no free lunch here.</p>
<p><code>__builtin_memcpy</code> is not a separate <code>memcpy</code> implementation. If <code>n</code> is small and known at compile time, the compiler expands it into a few load and store instructions. But if <code>n</code> is large or only known at runtime, it calls the actual <code>memcpy</code> from libc instead.</p>
<p>Even worse, you don't need to mention <code>memcpy</code> explicitly to use it. Suppose you copy a large struct like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span> <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">4096</span><span class="p">];</span> <span class="p">}</span> <span class="n">Big</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">copy</span><span class="p">(</span><span class="n">Big</span><span class="o">*</span> <span class="n">a</span><span class="p">,</span> <span class="k">const</span> <span class="n">Big</span><span class="o">*</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">*</span><span class="n">a</span> <span class="o">=</span> <span class="o">*</span><span class="n">b</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>When you compile the code for <code>aarch64-freestanding</code>, the object file contains an undefined reference to <code>memcpy</code>. Zero-initializing a local array produces the same issue with <code>memset</code>. Neither name appears in the source; both are introduced by the compiler.</p>
<p>So the freestanding environment must still provide <code>memcpy</code>, <code>memmove</code>, <code>memset</code>, and <code>memcmp</code> for memory operations to work in the general case.</p>
<p>WebAssembly covers three of the four: <code>memcpy</code>, <code>memmove</code>, and <code>memset</code> map to the <code>memory.copy</code> and <code>memory.fill</code> instructions. There is no instruction for comparison, so <code>memcmp</code> stays a real function call even there. On other targets, the toolchain often provides all four, as <code>zig cc</code> does (even with <code>-nostdlib</code>). If it doesn't, provide a plain C implementation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#undef memcpy
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="kt">void</span><span class="o">*</span> <span class="nf">memcpy</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">dst</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">src</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">d</span> <span class="o">=</span> <span class="n">dst</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">s</span> <span class="o">=</span> <span class="n">src</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">n</span><span class="o">--</span><span class="p">)</span> <span class="o">*</span><span class="n">d</span><span class="o">++</span> <span class="o">=</span> <span class="o">*</span><span class="n">s</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">dst</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#undef memset
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="kt">void</span><span class="o">*</span> <span class="nf">memset</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">dst</span><span class="p">,</span> <span class="kt">int</span> <span class="n">ch</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">d</span> <span class="o">=</span> <span class="n">dst</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">n</span><span class="o">--</span><span class="p">)</span> <span class="o">*</span><span class="n">d</span><span class="o">++</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)</span><span class="n">ch</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">dst</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#undef memmove
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="kt">void</span><span class="o">*</span> <span class="nf">memmove</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">dst</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">src</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// omitted for brevity
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#undef memcmp
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="kt">int</span> <span class="nf">memcmp</span><span class="p">(</span><span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">lhs</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">rhs</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">l</span> <span class="o">=</span> <span class="n">lhs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">r</span> <span class="o">=</span> <span class="n">rhs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;</span> <span class="n">n</span><span class="o">--</span><span class="p">;</span> <span class="n">l</span><span class="o">++</span><span class="p">,</span> <span class="n">r</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="n">l</span> <span class="o">!=</span> <span class="o">*</span><span class="n">r</span><span class="p">)</span> <span class="k">return</span> <span class="o">*</span><span class="n">l</span> <span class="o">-</span> <span class="o">*</span><span class="n">r</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The defines (<code>#define memcpy __builtin_memcpy</code> and others above) are still worth keeping, even if you implement the functions yourself. This way, the compiler can still use its own implementation when applicable.</p>
<blockquote>
<p>Fun fact: at <code>-O2</code> and above, GCC can fold your custom <code>memcpy</code> implementation back into a call to <code>memcpy</code>, which is infinite recursion. <code>-ffreestanding</code> prevents this because it implies <code>-fno-builtin</code>, but the guarantee is weak. You can use <code>-fno-tree-loop-distribute-patterns</code> to disable this behavior for good.</p>
</blockquote>
<p>The rest of <code>string.h</code> is not covered. No compiler provides <code>memchr</code> or <code>strlen</code>, so those you always have to write yourself — more on that below.</p>
<h2 id="atomic-operations">Atomic operations</h2>
<p>Another useful group of compiler builtins is <code>__atomic_xxx</code>, which provide atomic, thread-safe memory access. They operate on regular objects instead of <code>_Atomic</code> objects:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// so_atomic_load atomically loads the value at p.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define so_atomic_load(p) \
</span></span></span><span class="line"><span class="cl"><span class="cp">    (__atomic_load_n((p), __ATOMIC_SEQ_CST))
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// so_atomic_store atomically stores v at p.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define so_atomic_store(p, v) \
</span></span></span><span class="line"><span class="cl"><span class="cp">    (__atomic_store_n((p), (v), __ATOMIC_SEQ_CST))
</span></span></span></code></pre></div><p>This makes porting Go's <code>sync/atomic</code> types straightforward. All types — atomic integers, unsigned integers, booleans, and pointers — use the same two load/store macros:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Bool is an atomic boolean value. The zero value is false.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="n">atomic_Bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">v</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">atomic_Bool</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Load atomically loads and returns the value stored in x.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">bool</span> <span class="nf">atomic_Bool_Load</span><span class="p">(</span><span class="n">atomic_Bool</span><span class="o">*</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nf">so_atomic_load</span><span class="p">(</span><span class="o">&amp;</span><span class="n">x</span><span class="o">-&gt;</span><span class="n">v</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Store atomically stores val into x.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="nf">atomic_Bool_Store</span><span class="p">(</span><span class="n">atomic_Bool</span><span class="o">*</span> <span class="n">x</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">val</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_atomic_store</span><span class="p">(</span><span class="o">&amp;</span><span class="n">x</span><span class="o">-&gt;</span><span class="n">v</span><span class="p">,</span> <span class="n">val</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><blockquote>
<p>A separate <code>atomic_Bool</code> type isn't strictly required — <code>atomic_Bool_Load</code> and <code>atomic_Bool_Store</code> would work with a plain <code>bool*</code>. Still, it can be useful. With a plain pointer, <code>*x = true</code> creates a silent data race that looks like ordinary code, while the wrapper makes you explicitly write <code>x-&gt;v = true</code>.</p>
</blockquote>
<p>No <code>stdatomic.h</code> include is needed. However, the CPU must natively support the integer width you use. For example, a 64-bit atomic on a 32-bit target becomes a call to libatomic instead of a single instruction. But that's a different story.</p>
<h2 id="pure-c-implementations">Pure C implementations</h2>
<p>If the compiler doesn't provide an implementation, you have to write one yourself. Preferably, use the libc name so all call sites remain unchanged.</p>
<p>A good example is <code>memchr</code>, which is required by <code>bytes.IndexByte</code>. There is a <code>__builtin_memchr</code>, but it is not an implementation, so you need to provide your own:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#ifndef so_build_hosted
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="c1">// memchr implementation for freestanding environments.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span><span class="o">*</span> <span class="nf">memchr</span><span class="p">(</span><span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">s</span><span class="p">,</span> <span class="kt">int</span> <span class="n">c</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">p</span> <span class="o">=</span> <span class="n">s</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">target</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)</span><span class="n">c</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">n</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="n">p</span> <span class="o">==</span> <span class="n">target</span><span class="p">)</span> <span class="k">return</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">p</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">p</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span></code></pre></div><p>Some of these DIY implementations aren't trivial, of course. Fortunately, Go's standard library includes many standalone algorithms, such as the string-to-number conversion functions in <code>strconv</code> or integer math in <code>math/bits</code>. Porting them to C is almost mechanical:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Go version.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">const</span> <span class="nx">m3</span> <span class="p">=</span> <span class="mh">0x00ff00ff00ff00ff</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ReverseBytes32 returns the value of x
</span></span></span><span class="line"><span class="cl"><span class="c1">// with its bytes in reversed order.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">ReverseBytes32</span><span class="p">(</span><span class="nx">x</span> <span class="kt">uint32</span><span class="p">)</span> <span class="kt">uint32</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">const</span> <span class="nx">m</span> <span class="p">=</span> <span class="mi">1</span><span class="o">&lt;&lt;</span><span class="mi">32</span> <span class="o">-</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="nx">x</span> <span class="p">=</span> <span class="nx">x</span><span class="o">&gt;&gt;</span><span class="mi">8</span><span class="o">&amp;</span><span class="p">(</span><span class="nx">m3</span><span class="o">&amp;</span><span class="nx">m</span><span class="p">)</span> <span class="p">|</span> <span class="nx">x</span><span class="o">&amp;</span><span class="p">(</span><span class="nx">m3</span><span class="o">&amp;</span><span class="nx">m</span><span class="p">)</span><span class="o">&lt;&lt;</span><span class="mi">8</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">x</span><span class="o">&gt;&gt;</span><span class="mi">16</span> <span class="p">|</span> <span class="nx">x</span><span class="o">&lt;&lt;</span><span class="mi">16</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// C version.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">static</span> <span class="k">const</span> <span class="kt">int64_t</span> <span class="n">m3</span> <span class="o">=</span> <span class="mh">0x00ff00ff00ff00ff</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">uint32_t</span> <span class="nf">bits_ReverseBytes32</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">int64_t</span> <span class="n">m</span> <span class="o">=</span> <span class="p">((</span><span class="kt">int64_t</span><span class="p">)</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">32</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">x</span> <span class="o">=</span> <span class="p">((</span><span class="n">x</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">&amp;</span> <span class="p">(</span><span class="n">m3</span> <span class="o">&amp;</span> <span class="n">m</span><span class="p">))</span> <span class="o">|</span> <span class="p">((</span><span class="n">x</span> <span class="o">&amp;</span> <span class="p">(</span><span class="n">m3</span> <span class="o">&amp;</span> <span class="n">m</span><span class="p">))</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">x</span> <span class="o">&gt;&gt;</span> <span class="mi">16</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">x</span> <span class="o">&lt;&lt;</span> <span class="mi">16</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="memory-allocation">Memory allocation</h2>
<p>Memory allocation calls for a different technique. A naive approach would be to implement a freestanding <code>malloc</code> that uses a static buffer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">extern</span> <span class="kt">char</span> <span class="n">so_heap</span><span class="p">[</span><span class="n">SO_HEAP_SIZE</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="kt">size_t</span> <span class="n">so_heap_offset</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span><span class="o">*</span> <span class="nf">malloc</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Simplified version without alignment.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&gt;</span> <span class="n">SO_HEAP_SIZE</span> <span class="o">-</span> <span class="n">so_heap_offset</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">so_heap</span><span class="p">[</span><span class="n">so_heap_offset</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_heap_offset</span> <span class="o">+=</span> <span class="n">size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">ptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>It might be sufficient for testing, but I'd avoid using it in production.</p>
<p>Instead of reimplementing <code>malloc</code>, let's remove the need for it, and make the caller provide the memory. Start with an allocator interface, so callers don't depend on a specific implementation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Allocator defines the interface for memory allocators.
</span></span></span><span class="line"><span class="cl"><span class="c1">// Simplified version without Realloc and alignment.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_R_ptr_err</span> <span class="p">(</span><span class="o">*</span><span class="n">Alloc</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">Free</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">mem_Allocator</span><span class="p">;</span>
</span></span></code></pre></div><div class="boxed">
<p><strong>What's with the so-types?</strong></p>
<p><code>so_int</code> is an integer of the target width:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#if SIZE_MAX == 0xFFFFFFFFu
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="k">typedef</span> <span class="kt">int32_t</span> <span class="n">so_int</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="cp">#else
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="k">typedef</span> <span class="kt">int64_t</span> <span class="n">so_int</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span></code></pre></div><p><code>so_String</code> is a pointer to the underlying string bytes and their count:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">ptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">so_String</span><span class="p">;</span>
</span></span></code></pre></div><p><code>so_Error</code> is an interface value that wraps the error data:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_String</span> <span class="p">(</span><span class="o">*</span><span class="n">Error</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">so_Error</span><span class="p">;</span>
</span></span></code></pre></div><p><code>so_R_ptr_err</code> is a result-type implementation for a (pointer + error) pair:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">val</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Error</span> <span class="n">err</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">so_R_ptr_err</span><span class="p">;</span>
</span></span></code></pre></div><p>There are other similar types like <code>so_R_int_err</code> (int + error) or <code>so_R_f32_bool</code> (float32 + bool).</p>
</div>
<p>Then provide an arena allocator, which is freestanding by design:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Arena is a memory allocator that bump-allocates
</span></span></span><span class="line"><span class="cl"><span class="c1">// linearly within a fixed buffer.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">offset</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">mem_Arena</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">mem_Arena</span> <span class="nf">mem_NewArena</span><span class="p">(</span><span class="n">so_Slice</span> <span class="n">buf</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">mem_Arena</span><span class="p">){.</span><span class="n">buf</span> <span class="o">=</span> <span class="n">buf</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">so_R_ptr_err</span> <span class="nf">mem_Arena_Alloc</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Simplified version without alignment.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">mem_Arena</span><span class="o">*</span> <span class="n">a</span> <span class="o">=</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">assert</span><span class="p">(</span><span class="n">size</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="s">&#34;mem: invalid allocation size&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&gt;</span> <span class="nf">so_len</span><span class="p">(</span><span class="n">a</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">)</span> <span class="o">-</span> <span class="n">a</span><span class="o">-&gt;</span><span class="n">offset</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="n">so_R_ptr_err</span><span class="p">){.</span><span class="n">val</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="n">mem_ErrOutOfMemory</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span> <span class="o">=</span> <span class="o">&amp;</span><span class="nf">so_at</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">a</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">,</span> <span class="n">a</span><span class="o">-&gt;</span><span class="n">offset</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">a</span><span class="o">-&gt;</span><span class="n">offset</span> <span class="o">+=</span> <span class="n">size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">so_R_ptr_err</span><span class="p">){.</span><span class="n">val</span> <span class="o">=</span> <span class="n">ptr</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="p">(</span><span class="n">so_Error</span><span class="p">){}};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">mem_Arena_Free</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Free in arena is a no-op.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">self</span><span class="p">;</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">ptr</span><span class="p">;</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">mem_Arena_Reset</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Arena</span><span class="o">*</span> <span class="n">a</span> <span class="o">=</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">a</span><span class="o">-&gt;</span><span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Usage example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">Point</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">x</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">Point</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Prepare the arena.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">so_byte</span> <span class="n">data</span><span class="p">[</span><span class="mi">1024</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="n">so_Slice</span> <span class="n">buf</span> <span class="o">=</span> <span class="p">{.</span><span class="n">ptr</span> <span class="o">=</span> <span class="n">data</span><span class="p">,</span> <span class="p">.</span><span class="n">len</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">data</span><span class="p">)};</span>
</span></span><span class="line"><span class="cl"><span class="n">mem_Arena</span> <span class="n">arena</span> <span class="o">=</span> <span class="nf">mem_NewArena</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">mem_Allocator</span> <span class="n">alloc</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">self</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">arena</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">Alloc</span> <span class="o">=</span> <span class="n">mem_Arena_Alloc</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">Free</span> <span class="o">=</span> <span class="n">mem_Arena_Free</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Allocate a Point. mem_Alloc is a macro that calls
</span></span></span><span class="line"><span class="cl"><span class="c1">// the Alloc &#34;method&#34; and panics on failure.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">Point</span><span class="o">*</span> <span class="n">p</span> <span class="o">=</span> <span class="nf">mem_Alloc</span><span class="p">(</span><span class="n">Point</span><span class="p">,</span> <span class="n">alloc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">p</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">=</span> <span class="mi">11</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">p</span><span class="o">-&gt;</span><span class="n">y</span> <span class="o">=</span> <span class="mi">22</span><span class="p">;</span>
</span></span></code></pre></div><p>On a freestanding target, an arena is a better choice than a buffer-backed <code>malloc</code>, because the caller decides how much memory is available and when it's released.</p>
<h2 id="values-not-pointers">Values, not pointers</h2>
<p>Constructor functions in Go typically return a pointer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// A string reader.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">Reader</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">s</span>        <span class="kt">string</span>
</span></span><span class="line"><span class="cl">    <span class="nx">i</span>        <span class="kt">int64</span> <span class="c1">// current reading index
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">prevRune</span> <span class="kt">int</span>   <span class="c1">// index of previous rune; or &lt; 0
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// NewReader returns a new Reader reading from s.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">NewReader</span><span class="p">(</span><span class="nx">s</span> <span class="kt">string</span><span class="p">)</span> <span class="o">*</span><span class="nx">Reader</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="o">&amp;</span><span class="nx">Reader</span><span class="p">{</span><span class="nx">s</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This roughly translates to the following code, using the memory allocator from the previous section:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// A string reader.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_String</span> <span class="n">s</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int64_t</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">prevRune</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">strings_Reader</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// NewReader returns a new Reader reading from s.
</span></span></span><span class="line"><span class="cl"><span class="c1">// The returned reader is allocated; the caller owns it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">strings_Reader</span><span class="o">*</span> <span class="nf">strings_NewReader</span><span class="p">(</span><span class="n">mem_Allocator</span> <span class="n">alloc</span><span class="p">,</span> <span class="n">so_String</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">strings_Reader</span><span class="o">*</span> <span class="n">r</span> <span class="o">=</span> <span class="nf">mem_Alloc</span><span class="p">(</span><span class="n">strings_Reader</span><span class="p">,</span> <span class="n">alloc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">r</span><span class="o">-&gt;</span><span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">r</span><span class="o">-&gt;</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">r</span><span class="o">-&gt;</span><span class="n">prevRune</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">r</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Instead of blindly following Go idioms, it's better to get rid of allocations altogether and return a value:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// NewReader returns a new Reader reading from s.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">strings_Reader</span> <span class="nf">strings_NewReader</span><span class="p">(</span><span class="n">so_String</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">strings_Reader</span><span class="p">){.</span><span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">,</span> <span class="p">.</span><span class="n">prevRune</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This isn't a technique specific to writing freestanding code — it's helpful for almost any C library.</p>
<h2 id="target-hooks">Target hooks</h2>
<p>Some things you can't write in a target-agnostic way at all. Only the target knows how to print a byte, read the clock, or generate a random number; these all depend on the hardware.</p>
<p>What you can do is declare functions (hooks) and let the user's code define them:</p>
<table>
<thead>
<tr>
<th>Hook</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>so_write_out</code></td>
<td>send some bytes to the output</td>
</tr>
<tr>
<td><code>so_crand_read</code></td>
<td>read some random bytes</td>
</tr>
<tr>
<td><code>so_time_wall</code></td>
<td>get the current wall clock time</td>
</tr>
<tr>
<td><code>so_time_mono</code></td>
<td>get the current monotonic time</td>
</tr>
<tr>
<td><code>so_time_sleep</code></td>
<td>pause for a given duration</td>
</tr>
</tbody>
</table>
<p>Then the user can call specific APIs available on their hardware:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">so_int</span> <span class="nf">so_write_out</span><span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">buf</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nf">board_uart_write</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int64_t</span> <span class="nf">so_time_mono</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="kt">int64_t</span><span class="p">)</span><span class="nf">board_uptime_ms</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>What happens if the user doesn't provide an implementation? You still want the standard library to compile and work unless someone calls the missing functions. To achieve that, use weak definitions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// so_write_out drops the bytes and reports a full write,
</span></span></span><span class="line"><span class="cl"><span class="c1">// so panic and fmt print nothing and report no error.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nf">__attribute__</span><span class="p">((</span><span class="n">weak</span><span class="p">))</span> <span class="n">so_int</span> <span class="nf">so_write_out</span><span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">buf</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// so_crand_read reads no bytes. The interpretation is left to the caller.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nf">__attribute__</span><span class="p">((</span><span class="n">weak</span><span class="p">))</span> <span class="n">so_int</span> <span class="nf">so_crand_read</span><span class="p">(</span><span class="kt">uint8_t</span><span class="o">*</span> <span class="n">buf</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// so_time_wall panics, because no default date is correct.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nf">__attribute__</span><span class="p">((</span><span class="n">weak</span><span class="p">))</span> <span class="n">so_R_i64_i32</span> <span class="nf">so_time_wall</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_panic</span><span class="p">(</span><span class="s">&#34;time: define so_time_wall for this target&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now every hook gets a default, and a definition in the user code silently wins over the default one.</p>
<p>Note that the defaults above behave differently on purpose. Dropping output is fine because a board with no UART (serial interface) has nowhere to print. Inventing a date is not fine, because no date would be correct.</p>
<p>For the same reason, <code>crypto/rand</code> panics instead of falling back to a software generator. A &quot;random&quot; source that silently returns predictable bytes would be a terrible idea:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// crand_read fills buf with size cryptographically secure random bytes.
</span></span></span><span class="line"><span class="cl"><span class="c1">// Panics if the target does not define so_crand_read.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">crand_read</span><span class="p">(</span><span class="kt">uint8_t</span><span class="o">*</span> <span class="n">buf</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">so_crand_read</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">size</span><span class="p">)</span> <span class="o">!=</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">so_panic</span><span class="p">(</span><span class="s">&#34;crypto/rand: no entropy source&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You can still use a fallback when cryptographic security isn't needed, such as for hashing map keys or <code>math/rand</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// runtime_Seed returns a random 64-bit seed.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">static</span> <span class="kr">inline</span> <span class="kt">uint64_t</span> <span class="nf">runtime_Seed</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">seed</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Use cryptographically secure random if available.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="nf">so_crand_read</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">seed</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span> <span class="o">==</span> <span class="mi">8</span> <span class="o">&amp;&amp;</span> <span class="n">seed</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">seed</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Fallback to deterministic xorshift64 sequence.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><h2 id="hosted-only">Hosted-only</h2>
<p>Some things aren't worth solving with hooks, such as the <code>os</code> and <code>net</code> packages, which require a lot of target-specific code. In these cases, it's better to use a header-level guard that fails in freestanding mode:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// so/os/os.h
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#include</span> <span class="cpf">&#34;so/builtin/builtin.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifndef so_build_hosted
</span></span></span><span class="line"><span class="cl"><span class="cp">#error &#34;os: hosted environment required&#34;
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span></code></pre></div><p>If user code imports <code>os</code> in a freestanding environment, the compiler reports an error at compile time instead of at link time or runtime.</p>
<h2 id="testing">Testing</h2>
<p>The only way to know if the freestanding implementation actually works is to test it.</p>
<p>My approach in Solod is to run the freestanding packages' test suites with a WASI runtime and a small harness. The harness defines all five hooks from the <em>Target hooks</em> section as WASI imports:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// ciovec is the buffer descriptor that fd_write reads.
</span></span></span><span class="line"><span class="cl"><span class="c1">// The WASI ABI is 32-bit, so both fields are 32-bit.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">ciovec</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// wasi_fd_write writes the buffers to the file descriptor
</span></span></span><span class="line"><span class="cl"><span class="c1">// and stores the number of bytes written in nwritten.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nf">__attribute__</span><span class="p">((</span><span class="nf">import_module</span><span class="p">(</span><span class="s">&#34;wasi_snapshot_preview1&#34;</span><span class="p">),</span> <span class="nf">import_name</span><span class="p">(</span><span class="s">&#34;fd_write&#34;</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="kt">uint32_t</span> <span class="nf">wasi_fd_write</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">fd</span><span class="p">,</span> <span class="k">const</span> <span class="n">ciovec</span><span class="o">*</span> <span class="n">iovs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                              <span class="kt">uint32_t</span> <span class="n">iovs_len</span><span class="p">,</span> <span class="kt">uint32_t</span><span class="o">*</span> <span class="n">nwritten</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// so_write_out writes size bytes to the standard output of the WASI host.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">so_int</span> <span class="nf">so_write_out</span><span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">buf</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ciovec</span> <span class="n">iov</span> <span class="o">=</span> <span class="p">{.</span><span class="n">buf</span> <span class="o">=</span> <span class="n">buf</span><span class="p">,</span> <span class="p">.</span><span class="n">len</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span><span class="n">size</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">written</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">wasi_fd_write</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">iov</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">written</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">so_int</span><span class="p">)</span><span class="n">written</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The freestanding make task builds tests from stdlib packages into a single <code>wasm32-freestanding</code> binary and runs it with wasmtime. This uses the same tests for the freestanding logic as in hosted mode, so I don't need to write separate freestanding tests.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>Here's a summary of the approach I used write a freestanding stdlib in C:</p>
<ul>
<li>Choose between hosted and freestanding at compile time.</li>
<li>Use the compiler builtins when possible.</li>
<li>Implement the missing parts and port the standalone code.</li>
<li>Use explicit allocators; prefer values to pointers.</li>
<li>Declare hooks for the hardware, with weak defaults.</li>
<li>Fail fast for packages that can't work in freestanding.</li>
<li>Test in a freestanding build, not just hosted.</li>
</ul>
<p>I hope you find it useful too.</p>
<p>If you're interested in trying this in practice, take a look at Solod's <a href="https://github.com/solod-dev/solod#readme">README</a> — it has everything you need to get started. Or <a href="https://solod.dev/#try">try it online</a> without installing anything.</p>
]]></content:encoded></item><item><title>Relying on Go</title><link>https://antonz.org/relying-on-go/</link><pubDate>Sun, 09 Aug 2026 12:00:00 +0000</pubDate><guid>https://antonz.org/relying-on-go/</guid><description>Reusing Go's tooling and standard library for a systems language.</description><content:encoded><![CDATA[<p>Everyone is creating a new programming language these days, often one that's &quot;like Go but with more features&quot; or &quot;like Rust but simpler&quot;.</p>
<p><a href="https://solod.dev">Solod</a>, a systems language for C and Go developers, might look like one of those languages, but it takes a different approach.</p>
<h2 id="gos-tooling">Go's tooling</h2>
<p>Solod is not &quot;Go-like&quot; in the usual sense, nor is it an attempt to &quot;fix Go's mistakes&quot;. At the language level, Solod is literally a <em>subset</em> of Go. Solod reuses much of Go's existing tooling, including syntax highlighting, LSP, linters, and the package management system.</p>
<p>Take this quick-start guide, for example:</p>
<div class="boxed">
<p><strong>Quick start</strong></p>
<p>Install the So command line tool:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">go install solod.dev/cmd/so@latest
</span></span></code></pre></div><p>Create a new Go project and add the Solod dependency to use the So standard library:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">go mod init example
</span></span><span class="line"><span class="cl">go get solod.dev@latest
</span></span></code></pre></div><p>Write regular Go code, but use Solod packages instead of the standard Go packages:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="s">&#34;solod.dev/so/math&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ans</span> <span class="o">:=</span> <span class="nx">math</span><span class="p">.</span><span class="nf">Sqrt</span><span class="p">(</span><span class="mi">1764</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="s">&#34;Hello, world! The answer is&#34;</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="nx">ans</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Run without saving the binary:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">so run .
</span></span></code></pre></div><p>That's it!</p>
</div>
<p>There's nothing new here. It's mostly standard Go workflow, except for <code>so run</code>, which is a Go program that mimics <code>go run</code>.</p>
<h2 id="gos-standard-library">Go's standard library</h2>
<p>Solod also reuses a lot of Go's standard library code and tests. Some of it is taken verbatim from Go's source code, like these two string functions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// CutPrefix returns s without the provided leading prefix string
</span></span></span><span class="line"><span class="cl"><span class="c1">// and reports whether it found the prefix.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">CutPrefix</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span> <span class="nx">prefix</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">string</span><span class="p">,</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">!</span><span class="nf">HasPrefix</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span> <span class="nx">prefix</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">s</span><span class="p">,</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">s</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="nx">prefix</span><span class="p">):],</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// HasPrefix reports whether the string s begins with prefix.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">HasPrefix</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span> <span class="nx">prefix</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="nb">len</span><span class="p">(</span><span class="nx">prefix</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">s</span><span class="p">[:</span><span class="nb">len</span><span class="p">(</span><span class="nx">prefix</span><span class="p">)]</span> <span class="o">==</span> <span class="nx">prefix</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><blockquote>
<p>Of course, Solod retains the Go authors' copyright.</p>
</blockquote>
<p>Some code requires changes to support the manual memory management with explicit allocators used by Solod:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Go version.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Clone</span><span class="p">(</span><span class="nx">s</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">b</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="nb">copy</span><span class="p">(</span><span class="nx">b</span><span class="p">,</span> <span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">unsafe</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">b</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nb">len</span><span class="p">(</span><span class="nx">b</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Solod version.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Clone</span><span class="p">(</span><span class="nx">a</span> <span class="nx">mem</span><span class="p">.</span><span class="nx">Allocator</span><span class="p">,</span> <span class="nx">s</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">b</span> <span class="o">:=</span> <span class="nx">mem</span><span class="p">.</span><span class="nx">AllocSlice</span><span class="p">[</span><span class="kt">byte</span><span class="p">](</span><span class="nx">a</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="nb">copy</span><span class="p">(</span><span class="nx">b</span><span class="p">,</span> <span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">string</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You can probably see the resemblance.</p>
<h2 id="a-grain-of-salt">A grain of salt</h2>
<p>Go tools don't know that Solod is a subset of the full Go language, so they won't flag features Solod doesn't support, like function literals or iterators. These diagnostics come from the custom <code>so</code> tooling:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">f</span> <span class="o">:=</span> <span class="kd">func</span><span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">println</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nf">f</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">main.go:4:7: function literals are not supported
</span></span><span class="line"><span class="cl">    f := func(n int) {
</span></span><span class="line"><span class="cl">         ^here
</span></span></code></pre></div><p>Also, although a substantial part of Go's standard library is ported verbatim or with minimal changes from the original source, that doesn't mean the code is automatically correct. Solod still needs its own tests, including ones that run under sanitizers and static analyzers.</p>
<h2 id="its-all-c-in-the-end">It's all C in the end</h2>
<p>All Solod code is translated to regular C11 and then compiled with GCC or Clang. Solod therefore relies on C tooling and decades of optimization work just as much as on Go's.</p>
<p>Solod code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="s">&#34;solod.dev/so/math&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// What might it be?
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">ans</span> <span class="o">:=</span> <span class="nx">math</span><span class="p">.</span><span class="nf">Sqrt</span><span class="p">(</span><span class="mi">1764</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="s">&#34;Hello, world! The answer is&#34;</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="nx">ans</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Translated C code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// -- main.h --
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#pragma once
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;so/builtin/builtin.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;so/math/math.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// -- main.c --
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#include</span> <span class="cpf">&#34;main.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// What might it be?
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">double</span> <span class="n">ans</span> <span class="o">=</span> <span class="nf">math_Sqrt</span><span class="p">(</span><span class="mf">1764.0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_println</span><span class="p">(</span><span class="s">&#34;%s %&#34;</span> <span class="n">PRIdINT</span><span class="p">,</span> <span class="s">&#34;Hello, world! The answer is&#34;</span><span class="p">,</span> <span class="p">(</span><span class="n">so_int</span><span class="p">)(</span><span class="n">ans</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The C version is noisier, of course, especially for more complex programs than this one. But it remains readable.</p>
<p>And since there's no runtime, interoperability between Solod and C costs nothing.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>A new language doesn't necessarily need a new ecosystem.</p>
<p>Solod relies heavily on Go, and I see that as a strength, not a weakness. Reusing Go's proven tools and standard library makes Solod more reliable and easier to work with.</p>
<p>If you're interested, take a look at Solod's <a href="https://github.com/solod-dev/solod#readme">readme</a> — it has everything you need to get started. Or <a href="https://solod.dev/#try">try it online</a> without installing anything.</p>
]]></content:encoded></item><item><title>Going Backward: Reinventing Go's iterator</title><link>https://antonz.org/going-backward/</link><pubDate>Mon, 03 Aug 2026 12:00:00 +0000</pubDate><guid>https://antonz.org/going-backward/</guid><description>Building a generic iterator wheel from scratch.</description><content:encoded><![CDATA[<p>Go's standard library has a <code>slices</code> package with a function called <code>Backward</code>. It lets you iterate over the elements of a slice in reverse order:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Backward returns an iterator over index-value pairs in the slice,
</span></span></span><span class="line"><span class="cl"><span class="c1">// traversing it backward with descending indices.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nx">Backward</span><span class="p">[</span><span class="nx">Slice</span> <span class="p">~[]</span><span class="nx">E</span><span class="p">,</span> <span class="nx">E</span> <span class="nx">any</span><span class="p">](</span><span class="nx">s</span> <span class="nx">Slice</span><span class="p">)</span> <span class="nx">iter</span><span class="p">.</span><span class="nx">Seq2</span><span class="p">[</span><span class="kt">int</span><span class="p">,</span> <span class="nx">E</span><span class="p">]</span>
</span></span></code></pre></div><p>If you're not deeply familiar with generics and iterators, the natural reaction to this signature (and to the others in the <code>slices</code> package) is: &quot;couldn't this have been made simpler somehow?&quot;</p>
<p>To answer that, let's run a thought experiment. Let's picture ourselves as a distant ancestor, living in the pre-iterator era, who decided to implement <code>Backward</code> from scratch.</p>
<blockquote>
<p>Our imaginary ancestor doesn't work at Google, so don't project their decisions onto the Go development team. They had their own reasons — and no Jira.</p>
</blockquote>
<h2 id="1-a-slice-in-reverse">1. A slice in reverse</h2>
<p>A pleasant, sunny summer day, birds singing. You're at the keyboard as usual, and suddenly you decide to write a function for walking a slice in reverse order. Anything beats working on yet another Jira ticket.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Backward returns the slice in reverse order.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nx">Backward</span><span class="p">[</span><span class="nx">T</span> <span class="nx">any</span><span class="p">](</span><span class="nx">s</span> <span class="p">[]</span><span class="nx">T</span><span class="p">)</span> <span class="p">[]</span><span class="nx">T</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">n</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">res</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="nx">T</span><span class="p">,</span> <span class="nx">n</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="nx">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">--</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">res</span><span class="p">[</span><span class="nx">n</span><span class="o">-</span><span class="mi">1</span><span class="o">-</span><span class="nx">i</span><span class="p">]</span> <span class="p">=</span> <span class="nx">s</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">res</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Usage example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">s</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">11</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">55</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">b</span> <span class="o">:=</span> <span class="nf">Backward</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">// [55 44 33 22 11]
</span></span></span></code></pre></div><p>The implementation is simple and works reliably. There's one drawback, though: <code>Backward</code> creates a copy of the slice, which can be wasteful for large slices.</p>
<p>Besides, the sun has hidden behind a cloud, and it looks like rain is coming. You decide to work a bit more.</p>
<h2 id="2-gimme-gimme-gimme">2. Gimme, gimme, gimme</h2>
<p>To avoid copying the slice, you decide to return a closure that knows the current position in the original slice and returns the next element on each call:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Backward returns a function that, on each call, returns the next
</span></span></span><span class="line"><span class="cl"><span class="c1">// element of the slice (in reverse order) and a flag indicating
</span></span></span><span class="line"><span class="cl"><span class="c1">// whether to continue iterating (false means done).
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nx">Backward</span><span class="p">[</span><span class="nx">T</span> <span class="nx">any</span><span class="p">](</span><span class="nx">s</span> <span class="p">[]</span><span class="nx">T</span><span class="p">)</span> <span class="kd">func</span><span class="p">()</span> <span class="p">(</span><span class="nx">T</span><span class="p">,</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">i</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kd">func</span><span class="p">()</span> <span class="p">(</span><span class="nx">T</span><span class="p">,</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nx">i</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kd">var</span> <span class="nx">zero</span> <span class="nx">T</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nx">zero</span><span class="p">,</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nx">i</span><span class="o">--</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">s</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Usage example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">s</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">11</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">55</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">next</span> <span class="o">:=</span> <span class="nf">Backward</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">v</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nf">next</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">!</span><span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Print</span><span class="p">(</span><span class="nx">v</span><span class="p">,</span> <span class="s">&#34; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 55 44 33 22 11
</span></span></span></code></pre></div><p>Now it allocates O(1) memory instead of O(n). That's better.</p>
<p>Before moving on, you glance out of the window. Yep, sure enough, the rain has started, and the sky is even cloudier than before. Excellent working weather!</p>
<h2 id="3-a-callback-based-iterator">3. A callback-based iterator</h2>
<p>Something about the calling code keeps bothering you. It came out quite imperative. You'd like to hand the loop mechanics over to <code>Backward</code> and leave the caller with nothing but the application logic (whatever it is you do with the slice elements).</p>
<p>You decide to complicate <code>Backward</code>'s signature a little. Now it will return an iterator function that takes a callback as an argument and applies it to each element of the slice:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Backward returns a function that takes a yield callback.
</span></span></span><span class="line"><span class="cl"><span class="c1">// The callback is invoked for each element of the slice (in reverse order).
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nx">Backward</span><span class="p">[</span><span class="nx">T</span> <span class="nx">any</span><span class="p">](</span><span class="nx">s</span> <span class="p">[]</span><span class="nx">T</span><span class="p">)</span> <span class="kd">func</span><span class="p">(</span><span class="nx">yield</span> <span class="kd">func</span><span class="p">(</span><span class="nx">T</span><span class="p">)</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kd">func</span><span class="p">(</span><span class="nx">yield</span> <span class="kd">func</span><span class="p">(</span><span class="nx">T</span><span class="p">)</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">--</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">!</span><span class="nf">yield</span><span class="p">(</span><span class="nx">s</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>yield</code> function returns a <code>bool</code> — that's so the callback can signal when it wants to stop the traversal early.</p>
<p>Now you can turn the <code>for</code> loop body in the calling code into a callback, and you don't need the loop anymore:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">work</span> <span class="o">:=</span> <span class="kd">func</span><span class="p">(</span><span class="nx">x</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">x</span> <span class="p">&lt;</span> <span class="mi">30</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">false</span> <span class="c1">// early exit
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Print</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="s">&#34; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">s</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">11</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">55</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">it</span> <span class="o">:=</span> <span class="nf">Backward</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">it</span><span class="p">(</span><span class="nx">work</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 55 44 33
</span></span></span></code></pre></div><p>Mmm, very functional.</p>
<p>One small nuance: <code>Backward</code>'s signature looks a bit heavy. You add a separate type for the return value:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Seq is an iterator over sequences of individual values.
</span></span></span><span class="line"><span class="cl"><span class="c1">// When called as seq(yield), seq calls yield(v) for each value
</span></span></span><span class="line"><span class="cl"><span class="c1">// v in the sequence, stopping early if yield returns false.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">Seq</span><span class="p">[</span><span class="nx">T</span> <span class="nx">any</span><span class="p">]</span> <span class="kd">func</span><span class="p">(</span><span class="nx">yield</span> <span class="kd">func</span><span class="p">(</span><span class="nx">T</span><span class="p">)</span> <span class="kt">bool</span><span class="p">)</span>
</span></span></code></pre></div><p>The function looks much better now:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nx">Backward</span><span class="p">[</span><span class="nx">T</span> <span class="nx">any</span><span class="p">](</span><span class="nx">s</span> <span class="p">[]</span><span class="nx">T</span><span class="p">)</span> <span class="nx">Seq</span><span class="p">[</span><span class="nx">T</span><span class="p">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// body unchanged
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>Praising yourself for inventing the iterator, you walk over to the window. It looks like the weather's gotten worse. The rain is coming down in buckets, and the sky is so overcast that it's grown as dark as evening.</p>
<h2 id="4-iterator-2-return-of-the-iterator">4. Iterator 2: Return of the Iterator</h2>
<p>It's all great, but then it hits you: an ordinary <code>range</code> over a slice returns both the index and the element's value. Your iterator returns only the value. You decide to fix this vexing oversight:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nx">Backward</span><span class="p">[</span><span class="nx">T</span> <span class="nx">any</span><span class="p">](</span><span class="nx">s</span> <span class="p">[]</span><span class="nx">T</span><span class="p">)</span> <span class="kd">func</span><span class="p">(</span><span class="nx">yield</span> <span class="kd">func</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="nx">T</span><span class="p">)</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kd">func</span><span class="p">(</span><span class="nx">yield</span> <span class="kd">func</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="nx">T</span><span class="p">)</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">--</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">!</span><span class="nf">yield</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">s</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Usage example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">work</span> <span class="o">:=</span> <span class="kd">func</span><span class="p">(</span><span class="nx">i</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">x</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Print</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="s">&#34;:&#34;</span><span class="p">,</span> <span class="nx">x</span><span class="p">,</span> <span class="s">&#34; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">s</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">11</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">55</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">it</span> <span class="o">:=</span> <span class="nf">Backward</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">it</span><span class="p">(</span><span class="nx">work</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 4:55 3:44 2:33 1:22 0:11
</span></span></span></code></pre></div><p>Since the result's signature has changed, it no longer fits the <code>Seq</code> type. What can you do — you'll have to add a new type. After ten minutes of deliberation, you decide to call it <code>Seq2</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Seq2 is an iterator over sequences of key-value pairs.
</span></span></span><span class="line"><span class="cl"><span class="c1">// When called as seq(yield), seq calls yield(k, v) for each pair
</span></span></span><span class="line"><span class="cl"><span class="c1">// (k, v) in the sequence, stopping early if yield returns false.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">Seq2</span><span class="p">[</span><span class="nx">K</span> <span class="nx">any</span><span class="p">,</span> <span class="nx">V</span> <span class="nx">any</span><span class="p">]</span> <span class="kd">func</span><span class="p">(</span><span class="nx">yield</span> <span class="kd">func</span><span class="p">(</span><span class="nx">K</span><span class="p">,</span> <span class="nx">V</span><span class="p">)</span> <span class="kt">bool</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nx">Backward</span><span class="p">[</span><span class="nx">T</span> <span class="nx">any</span><span class="p">](</span><span class="nx">s</span> <span class="p">[]</span><span class="nx">T</span><span class="p">)</span> <span class="nx">Seq2</span><span class="p">[</span><span class="kt">int</span><span class="p">,</span> <span class="nx">T</span><span class="p">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// body unchanged
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>You get up to stretch your legs, and go to the window. The downpour is so heavy you can't make anything out. Lightning is flashing. Hail the size of your fist is falling — you've never seen anything like it in your life. Well, these things happen!</p>
<h2 id="5-not-quite-a-slice">5. Not quite a slice</h2>
<p>Have you thought of everything? Seems so. But you're not going back to Jira tickets just yet. Refreshing your memory of the Go spec, you realize that besides ordinary slices there are &quot;user-defined&quot; ones — types whose underlying type is a slice:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// IDs is a slice of identifiers.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">IDs</span> <span class="p">[]</span><span class="kt">int</span>
</span></span></code></pre></div><p><code>Backward</code> works perfectly well with <code>IDs</code> — the compiler accepts a value of type <code>IDs</code> since its underlying type is <code>[]int</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ids</span> <span class="o">:=</span> <span class="nx">IDs</span><span class="p">{</span><span class="mi">11</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">55</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">it</span> <span class="o">:=</span> <span class="nf">Backward</span><span class="p">(</span><span class="nx">ids</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">it</span><span class="p">(</span><span class="nx">work</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 4:55 3:44 2:33 1:22 0:11
</span></span></span></code></pre></div><p>But what about this?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// backwardIDs builds an iterator over a slice of identifiers
</span></span></span><span class="line"><span class="cl"><span class="c1">// in reverse order.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="nx">backwardIDs</span> <span class="kd">func</span><span class="p">(</span><span class="nx">IDs</span><span class="p">)</span> <span class="nx">Seq2</span><span class="p">[</span><span class="kt">int</span><span class="p">,</span> <span class="kt">int</span><span class="p">]</span> <span class="p">=</span> <span class="nx">Backward</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c1">// ERROR: cannot use Backward[int]
</span></span></span><span class="line"><span class="cl"><span class="c1">// (value of type func(s []int) Seq2[int, int])
</span></span></span><span class="line"><span class="cl"><span class="c1">// as func(IDs) Seq2[int, int] value in variable declaration
</span></span></span></code></pre></div><p>Here's where the difference between <code>IDs</code> and <code>[]int</code> shows up.</p>
<p>When you assign the function itself, it's the signatures that get compared: <code>func(IDs) Seq2[int, int]</code> versus <code>func([]int) Seq2[int, int]</code>. Signatures match only if the parameter types are identical. But <code>IDs</code> and <code>[]int</code> are different, even though one is based on the other. The signatures differ → you get an error.</p>
<p>Scratching your head, you turn to the spec once again and find a special generic syntax: <code>~T</code>. It represents the set of all types whose underlying type is <code>T</code>. Just what you need!</p>
<p>Now you'll have to parameterize not only the element type (<code>E</code>) but the slice type (<code>Slice</code>) as well. <code>E</code> is needed for the returned values, while <code>Slice</code> lets the function accept not just <code>[]E</code>, but any types based on it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nx">Backward</span><span class="p">[</span><span class="nx">Slice</span> <span class="p">~[]</span><span class="nx">E</span><span class="p">,</span> <span class="nx">E</span> <span class="nx">any</span><span class="p">](</span><span class="nx">s</span> <span class="nx">Slice</span><span class="p">)</span> <span class="nx">Seq2</span><span class="p">[</span><span class="kt">int</span><span class="p">,</span> <span class="nx">E</span><span class="p">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kd">func</span><span class="p">(</span><span class="nx">yield</span> <span class="kd">func</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="nx">E</span><span class="p">)</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">--</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">!</span><span class="nf">yield</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">s</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now the example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">backwardIDs</span> <span class="kd">func</span><span class="p">(</span><span class="nx">IDs</span><span class="p">)</span> <span class="nx">Seq2</span><span class="p">[</span><span class="kt">int</span><span class="p">,</span> <span class="kt">int</span><span class="p">]</span> <span class="p">=</span> <span class="nx">Backward</span><span class="p">[</span><span class="nx">IDs</span><span class="p">,</span> <span class="kt">int</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">ids</span> <span class="o">:=</span> <span class="nx">IDs</span><span class="p">{</span><span class="mi">11</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">55</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">work</span> <span class="o">:=</span> <span class="kd">func</span><span class="p">(</span><span class="nx">i</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">x</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Print</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="s">&#34;:&#34;</span><span class="p">,</span> <span class="nx">x</span><span class="p">,</span> <span class="s">&#34; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">it</span> <span class="o">:=</span> <span class="nf">backwardIDs</span><span class="p">(</span><span class="nx">ids</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">it</span><span class="p">(</span><span class="nx">work</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 4:55 3:44 2:33 1:22 0:11
</span></span></span></code></pre></div><p>It works! You've ended up with something similar to <code>Backward</code> from the <code>slices</code> package.</p>
<p>You exhale wearily and walk over to the window. The downpour and hail have given way to a hurricane. Trees and billboards go flying past. Toads, for some reason, are falling from the sky.</p>
<h2 id="6-iterator-3-judgment-day">6. Iterator 3: Judgment Day</h2>
<p>To take your mind off the strange events outside the window, you keep pondering.</p>
<p>An ordinary <code>Backward</code> is already great. But it would be even better if the traversal logic itself were configurable. On the other hand, if you end up with a lot of parameters, a strategy would suit better. And, by the way, it wouldn't hurt to add a factory that produces iterator factories according to given criteria...</p>
<p>Before you can finish the thought, the ground outside the window tears open with a deafening roar. An enormous black hand, streaming molten lava and flickering flames, bursts out of the fissure, seizes you, and drags you straight down to hell.</p>
<blockquote>
<p>P.S. Despite the article's tongue-in-cheek tone, the &quot;complicated&quot; version in the standard library <a href="https://go.dev/blog/deconstructing-type-parameters">is justified</a> (<code>Backward</code> just follows suite with other package functions). But if you're doing something similar in a project that solves a specific problem — it might make sense to stop at the simpler option.</p>
</blockquote>
]]></content:encoded></item><item><title>Solod 0.3: Concurrency, JSON, more safety</title><link>https://antonz.org/solod-0.3/</link><pubDate>Sat, 25 Jul 2026 11:30:00 +0000</pubDate><guid>https://antonz.org/solod-0.3/</guid><description>A strict subset of Go that translates to regular C.</description><content:encoded><![CDATA[<p>Solod (<strong>So</strong>) is a subset of Go that translates to regular C — with zero runtime, manual memory management, and source-level interop. It's designed for two main audiences:</p>
<ul>
<li>Go developers who want low-level control without having to learn another language.</li>
<li>C developers who like Go's style.</li>
</ul>
<p>At the end of the <a href="/solod-0.2">v0.2 post</a>, I said the obvious goal for the next release was concurrency, along with the stdlib packages that support it. That's what v0.3 is about. So now has threads, channels, worker pools, mutexes, and atomics — enough tools for parallel data processing or handling network connections.</p>
<p>This release also adds a streaming JSON package, a bunch of safety checks (escape analysis, leak checking, nil-pointer panics, stack traces), and proper <code>so test</code> and <code>so bench</code> commands.</p>
<p><a href="#threads">Threads</a> •
<a href="#channels">Channels</a> •
<a href="#worker-pools">Worker pools</a> •
<a href="#sharing-state">Sharing state</a> •
<a href="#json">JSON</a> •
<a href="#safety-net">Safety net</a> •
<a href="#tooling">Tooling</a> •
<a href="#wrapping-up">Wrapping up</a></p>
<h2 id="threads">Threads</h2>
<p>The new <code>conc</code> package is the foundation. It provides real OS threads, backed by pthreads. If you're familiar with Go's goroutines, the code will look similar — but there are some important differences.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// greet prints a label three times.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">greet</span><span class="p">(</span><span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span> <span class="nx">any</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">from</span> <span class="o">:=</span> <span class="nx">arg</span><span class="p">.(</span><span class="kt">string</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="mi">3</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">println</span><span class="p">(</span><span class="nx">from</span><span class="p">,</span> <span class="s">&#34;-&gt;&#34;</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Run greet on a separate OS thread, concurrently with main.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">name</span> <span class="o">:=</span> <span class="s">&#34;thread&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">th</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="nx">greet</span><span class="p">,</span> <span class="nx">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Wait blocks until the thread finishes.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">th</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="s">&#34;done&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="header.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">thread -&gt; 0
</span></span><span class="line"><span class="cl">thread -&gt; 1
</span></span><span class="line"><span class="cl">thread -&gt; 2
</span></span><span class="line"><span class="cl">done
</span></span></code></pre></div><p>Solod doesn't support closures, so <code>conc.Go</code> takes a function and an <code>any</code> argument, instead of just a <code>func()</code> like you'd expect in Go.</p>
<p>Other important differences: starting an OS thread isn't free, and you always have to <code>Wait</code> on it (or <code>Detach</code> it), or it will leak. That makes <code>conc.Go</code> a good fit for a small, fixed number of long-lived threads — but not for thousands of short-lived tasks. For those cases, it's better to use a pool (shown below).</p>
<h2 id="channels">Channels</h2>
<p>Threads in Solod communicate with each other through channels, like goroutines in Go. A channel carries values of a specific type. By default, sending or receiving on a channel blocks until both sides are ready, so a channel also works as a synchronization point.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ping sends a single message on the given channel.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">ping</span><span class="p">(</span><span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span> <span class="nx">any</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">messages</span> <span class="o">:=</span> <span class="nx">arg</span><span class="p">.(</span><span class="o">*</span><span class="nx">conc</span><span class="p">.</span><span class="nx">Chan</span><span class="p">[</span><span class="kt">string</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="nx">messages</span><span class="p">.</span><span class="nf">Send</span><span class="p">(</span><span class="s">&#34;ping&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// An unbuffered channel (buffer size 0): each send blocks
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// until a receiver is ready to take the value.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">messages</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nx">NewChan</span><span class="p">[</span><span class="kt">string</span><span class="p">](</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nx">messages</span><span class="p">.</span><span class="nf">Free</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Launch a thread that sends &#34;ping&#34; into the channel.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">th</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="nx">ping</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">messages</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nx">th</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Receive the message and print it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kd">var</span> <span class="nx">msg</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl">    <span class="nx">messages</span><span class="p">.</span><span class="nf">Recv</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">msg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="nx">msg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="header.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ping
</span></span></code></pre></div><p>A couple of So-specific moments here. When you create a channel, you give it an allocator (<code>mem.System</code> in this case), and you call <code>Free</code> when you're done with it. Also, <code>Recv</code> writes to a pointer you pass in, instead of returning the value directly. It returns a <code>bool</code>, which is <code>false</code> when the channel is closed and empty. So, a typical <code>for msg := range ch</code> loop in Go becomes <code>for ch.Recv(&amp;msg) { ... }</code> in Solod.</p>
<blockquote>
<p>Allocators are a key concept in Solod. The language doesn't allow hidden heap allocations, so any function that needs to allocate memory must take an allocator (the <code>mem.Allocator</code> interface) as its first argument.</p>
</blockquote>
<p>Buffered channels can hold a limited number of values without having a receiver ready — just pass a non-zero size with <code>NewChan</code>. If you don't want to block forever, use <code>RecvTimeout</code> or <code>SendTimeout</code> with a duration. They return <code>conc.Ok</code> or <code>conc.Timeout</code> instead of getting stuck.</p>
<h2 id="worker-pools">Worker pools</h2>
<p>Threads are expensive, so spawning one per task doesn't scale. For handling many short-lived tasks, use <code>conc.Pool</code>: it uses a fixed number of worker threads that take tasks from a queue.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// job holds input and the result.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">job</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">id</span>     <span class="kt">int</span>
</span></span><span class="line"><span class="cl">    <span class="nx">result</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// process handles one job.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">process</span><span class="p">(</span><span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">j</span> <span class="o">:=</span> <span class="nx">arg</span><span class="p">.(</span><span class="o">*</span><span class="nx">job</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">100</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">j</span><span class="p">.</span><span class="nx">result</span> <span class="p">=</span> <span class="nx">j</span><span class="p">.</span><span class="nx">id</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// A pool of 4 worker threads. Each submitted job is handled
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// by the next available worker.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">pool</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nf">NewPool</span><span class="p">(</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="nx">conc</span><span class="p">.</span><span class="nx">PoolOptions</span><span class="p">{</span><span class="nx">NumThreads</span><span class="p">:</span> <span class="mi">4</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nx">pool</span><span class="p">.</span><span class="nf">Free</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Submit 8 jobs. Each writes into its own struct, so keep
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// the structs alive in a slice until the jobs finish.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">start</span> <span class="o">:=</span> <span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="nx">jobs</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="nx">job</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">jobs</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">jobs</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">id</span> <span class="p">=</span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="nx">pool</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="nx">process</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">jobs</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Wait until all submitted jobs have finished.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">pool</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">jobs</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">println</span><span class="p">(</span><span class="s">&#34;job&#34;</span><span class="p">,</span> <span class="nx">jobs</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">id</span><span class="p">,</span> <span class="s">&#34;-&gt;&#34;</span><span class="p">,</span> <span class="nx">jobs</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">result</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">elapsed</span> <span class="o">:=</span> <span class="nx">time</span><span class="p">.</span><span class="nf">Since</span><span class="p">(</span><span class="nx">start</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1_000_000</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="s">&#34;took&#34;</span><span class="p">,</span> <span class="nx">elapsed</span><span class="p">,</span> <span class="s">&#34;ms&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="header.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">job 1 -&gt; 2
</span></span><span class="line"><span class="cl">job 2 -&gt; 4
</span></span><span class="line"><span class="cl">job 3 -&gt; 6
</span></span><span class="line"><span class="cl">job 4 -&gt; 8
</span></span><span class="line"><span class="cl">job 5 -&gt; 10
</span></span><span class="line"><span class="cl">job 6 -&gt; 12
</span></span><span class="line"><span class="cl">job 7 -&gt; 14
</span></span><span class="line"><span class="cl">job 8 -&gt; 16
</span></span><span class="line"><span class="cl">took 200 ms
</span></span></code></pre></div><p><code>pool.Wait()</code> works similar to Go's <code>WaitGroup.Wait</code> — it blocks until all submitted jobs are finished. This program takes about 200 ms to run (even though there's 800 ms of total work), because 4 workers run concurrently.</p>
<p>You might think OS threads are much slower than Go's goroutines, but for pools, that's not the case. On realistic workloads, <code>conc.Pool</code> is usually only about 10% slower than Go, whether the tasks are CPU-bound or waiting on I/O. Channels are a different story: handing off work between threads requires a kernel wakeup, while Go does this in user space, so it can be several times slower. Check out <a href="/concurrency-in-c">Go-flavored concurrency in C</a> for more details.</p>
<h2 id="sharing-state">Sharing state</h2>
<p>One way to share state in Solod is by using channels to communicate it. However, sometimes you just need a shared counter or a lock. For that, the new release introduces the <code>sync</code> and <code>sync/atomic</code> packages.</p>
<p>Here's an example of an atomic counter being updated by 50 tasks running on 4 threads:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// increment atomically increases the shared counter 1000 times.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">increment</span><span class="p">(</span><span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ops</span> <span class="o">:=</span> <span class="nx">arg</span><span class="p">.(</span><span class="o">*</span><span class="nx">atomic</span><span class="p">.</span><span class="nx">Uint64</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="k">range</span> <span class="mi">1000</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">ops</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// An atomic value is safe for concurrent reads and writes.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kd">var</span> <span class="nx">ops</span> <span class="nx">atomic</span><span class="p">.</span><span class="nx">Uint64</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">pool</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nf">NewPool</span><span class="p">(</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="nx">conc</span><span class="p">.</span><span class="nx">PoolOptions</span><span class="p">{</span><span class="nx">NumThreads</span><span class="p">:</span> <span class="mi">4</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nx">pool</span><span class="p">.</span><span class="nf">Free</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// 50 tasks, each incrementing the counter 1000 times.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="k">range</span> <span class="mi">50</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">pool</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="nx">increment</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">ops</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">pool</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="s">&#34;ops:&#34;</span><span class="p">,</span> <span class="nx">ops</span><span class="p">.</span><span class="nf">Load</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="header.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ops: 50000
</span></span></code></pre></div><p>A regular <code>int</code> incremented with <code>ops++</code> would cause a data race and give a different result each time. Here, the result is exactly 50,000 on every run, thanks to the atomic <code>Uint64</code> counter. The <code>atomic</code> package provides <code>Int64</code>, <code>Uint64</code>, <code>Bool</code>, and <code>Pointer[T]</code> types, all of which are lock-free and safe for concurrent use.</p>
<p>For anything more complex than a counter, use <code>sync</code>, which provides <code>Mutex</code>, <code>Cond</code> (a condition variable), and <code>Once</code> (runs a function exactly once). One thing to watch out for: unlike Go, a So mutex's zero value isn't ready to use — you need to <code>Init</code> it before locking and <code>Free</code> it when done.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">mu</span> <span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span>
</span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Init</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">defer</span> <span class="nx">mu</span><span class="p">.</span><span class="nf">Free</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">defer</span> <span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">// ... critical section ...
</span></span></span></code></pre></div><h2 id="json">JSON</h2>
<p>Go's <code>encoding/json</code> relies on reflection to marshal arbitrary structs. Solod has no reflection, and uses a different approach: a token-level API. You read and write one JSON token at a time, and the <code>Encoder</code> and <code>Decoder</code> types take care of the syntax — adding commas and colons, checking UTF-8, and rejecting bad input.</p>
<p>Encoding is done through a series of calls that match the structure of your document:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">out</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="mi">256</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">sb</span> <span class="o">:=</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">FixedBuilder</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">enc</span> <span class="o">:=</span> <span class="nx">json</span><span class="p">.</span><span class="nf">NewEncoder</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">sb</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">enc</span><span class="p">.</span><span class="nf">BeginObject</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nx">enc</span><span class="p">.</span><span class="nf">Str</span><span class="p">(</span><span class="s">&#34;name&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">enc</span><span class="p">.</span><span class="nf">Str</span><span class="p">(</span><span class="s">&#34;Alice&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">enc</span><span class="p">.</span><span class="nf">Str</span><span class="p">(</span><span class="s">&#34;age&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">enc</span><span class="p">.</span><span class="nf">Int</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">enc</span><span class="p">.</span><span class="nf">EndObject</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nx">enc</span><span class="p">.</span><span class="nf">Flush</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">println</span><span class="p">(</span><span class="nx">sb</span><span class="p">.</span><span class="nf">String</span><span class="p">())</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="main.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;name&#34;:&#34;Alice&#34;,&#34;age&#34;:25}
</span></span></code></pre></div><p>Decoding pulls one validated token at a time with <code>Next</code>. You can check each token with <code>Kind</code> and read its value using typed getters like <code>Str</code>, <code>Int</code>, or <code>Bool</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">src</span> <span class="o">:=</span> <span class="s">`{&#34;name&#34;:&#34;Alice&#34;,&#34;age&#34;:25}`</span>
</span></span><span class="line"><span class="cl"><span class="nx">dec</span> <span class="o">:=</span> <span class="nx">json</span><span class="p">.</span><span class="nf">NewDecoder</span><span class="p">(</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="p">[]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">src</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="k">defer</span> <span class="nx">dec</span><span class="p">.</span><span class="nf">Free</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">name</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">age</span> <span class="kt">int64</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">dec</span><span class="p">.</span><span class="nf">Next</span><span class="p">()</span> <span class="c1">// the opening {
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">for</span> <span class="nx">dec</span><span class="p">.</span><span class="nf">Next</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="nx">dec</span><span class="p">.</span><span class="nf">Kind</span><span class="p">()</span> <span class="o">==</span> <span class="nx">json</span><span class="p">.</span><span class="nx">KindString</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="nx">dec</span><span class="p">.</span><span class="nf">Str</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="s">&#34;name&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nx">dec</span><span class="p">.</span><span class="nf">Next</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="nx">name</span> <span class="p">=</span> <span class="nx">dec</span><span class="p">.</span><span class="nf">Str</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="s">&#34;age&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nx">dec</span><span class="p">.</span><span class="nf">Next</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="nx">age</span> <span class="p">=</span> <span class="nx">dec</span><span class="p">.</span><span class="nf">Int</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nx">dec</span><span class="p">.</span><span class="nf">Next</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="nx">dec</span><span class="p">.</span><span class="nf">Skip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">println</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">age</span><span class="p">)</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="main.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Alice 25
</span></span></code></pre></div><blockquote>
<p>This is a simplified example that works only because the decoder doesn't allocate any memory. In a real-world situation, you'd need to <a href="https://github.com/solod-dev/example/blob/main/stdlib/json/main.go">use an allocator</a>.</p>
</blockquote>
<p>The decoder works the same way whether you're using an in-memory document (<code>NewDecoder</code>) or reading from a stream with an <code>io.Reader</code> (<code>NewReader</code>). This means you can decode data directly from a source without having to buffer the entire message first. Both the encoder and decoder use minimal memory and will reject invalid JSON or non-UTF-8 strings.</p>
<p>As you can see, API is low-level and not nearly as ergonomic as it is in Go, especially when it comes to decoding. But on the bright side, it's <a href="https://github.com/solod-dev/solod/blob/main/doc/benchmarks.md#json-encoding">10 times faster</a> and almost doesn't allocate, unlike in Go.</p>
<h2 id="safety-net">Safety net</h2>
<p>Solod compiles to plain C, which is fast but not very forgiving: if you use an out-of-bounds index, dereference nil, or divide by zero, you get undefined behavior that could crash the program or silently give wrong results. The new release addresses some of these issues.</p>
<p><strong>Escape analysis</strong>. Returning a pointer to a stack-allocated value is a classic C footgun. So now catches the common cases at compile time:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Point</span> <span class="kd">struct</span><span class="p">{</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">y</span> <span class="kt">int</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">newPoint</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span> <span class="kt">int</span><span class="p">)</span> <span class="o">*</span><span class="nx">Point</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="o">&amp;</span><span class="nx">Point</span><span class="p">{</span><span class="nx">x</span><span class="p">:</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">:</span> <span class="nx">y</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//     ^ compile-time error: stack-allocated
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">//       value escapes function frame
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">p</span> <span class="o">:=</span> <span class="nf">newPoint</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="nx">p</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> <span class="nx">p</span><span class="p">.</span><span class="nx">y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="header.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">so run: /tmp/sandbox/main.go:26:12: stack-allocated value escapes function frame
</span></span><span class="line"><span class="cl">    return &amp;Point{x: x, y: y}
</span></span><span class="line"><span class="cl">           ^here (exit status 1)
</span></span></code></pre></div><p>While the escape analyzer doesn't catch every case, it's still quite useful in practice. I actually found a couple of dangling pointers in the standard library code with it, even though I was sure there weren't any.</p>
<p><strong>Leak detection</strong>. Solod has no garbage collector, so a forgotten <code>Free</code> is a real memory leak. <code>mem.Tracker</code> helps catch these leaks: it wraps an allocator and keeps track of every allocation and free that goes through it. This way, you can monitor the program's memory usage in real time instead of guessing.</p>
<p>Wrap <code>mem.System</code> once, allocate memory through the tracker, and have a background thread log the stats at regular intervals:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// monitor periodically logs live allocation stats.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">monitor</span><span class="p">(</span><span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span> <span class="nx">any</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">t</span> <span class="o">:=</span> <span class="nx">arg</span><span class="p">.(</span><span class="o">*</span><span class="nx">mem</span><span class="p">.</span><span class="nx">Tracker</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">100</span> <span class="o">*</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nx">s</span> <span class="o">:=</span> <span class="nx">t</span><span class="p">.</span><span class="nf">Stats</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="nb">println</span><span class="p">(</span><span class="s">&#34;live:&#34;</span><span class="p">,</span> <span class="nx">s</span><span class="p">.</span><span class="nx">Mallocs</span><span class="o">-</span><span class="nx">s</span><span class="p">.</span><span class="nx">Frees</span><span class="p">,</span> <span class="s">&#34;allocations,&#34;</span><span class="p">,</span> <span class="nx">s</span><span class="p">.</span><span class="nx">Alloc</span><span class="p">,</span> <span class="s">&#34;bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Wrap the system allocator to count every allocation and free.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">heap</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">mem</span><span class="p">.</span><span class="nx">Tracker</span><span class="p">{</span><span class="nx">Allocator</span><span class="p">:</span> <span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Watch memory from a background thread.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">conc</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="nx">monitor</span><span class="p">,</span> <span class="nx">heap</span><span class="p">).</span><span class="nf">Detach</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Allocate through heap so the monitor sees it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="mi">10</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">v</span> <span class="o">:=</span> <span class="nx">mem</span><span class="p">.</span><span class="nx">Alloc</span><span class="p">[</span><span class="kt">int</span><span class="p">](</span><span class="nx">heap</span><span class="p">)</span> <span class="c1">// intentionally not freeing it
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="o">*</span><span class="nx">v</span> <span class="p">=</span> <span class="nx">i</span>
</span></span><span class="line"><span class="cl">        <span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">50</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="header.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">live: 2 allocations, 16 bytes
</span></span><span class="line"><span class="cl">live: 4 allocations, 32 bytes
</span></span><span class="line"><span class="cl">live: 6 allocations, 48 bytes
</span></span><span class="line"><span class="cl">live: 8 allocations, 64 bytes
</span></span><span class="line"><span class="cl">live: 10 allocations, 80 bytes
</span></span></code></pre></div><p>The tracker is lock-free and only uses a few atomic operations for each allocation, so it's cheap enough to keep enabled in production.</p>
<p><strong>Nil-pointer panics</strong>. If you try to dereference a nil pointer, it will cause a panic at runtime instead of a raw segmentation fault:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Rect</span> <span class="kd">struct</span><span class="p">{</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span> <span class="kt">int</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">r</span> <span class="o">*</span><span class="nx">Rect</span><span class="p">)</span> <span class="nf">area</span><span class="p">()</span> <span class="kt">int</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">r</span><span class="p">.</span><span class="nx">width</span> <span class="o">*</span> <span class="nx">r</span><span class="p">.</span><span class="nx">height</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//     ^ runtime error: nil pointer dereference
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">Rect</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nf">area</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="header.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">panic: nil pointer dereference
</span></span></code></pre></div><p><strong>Stack traces</strong>. When a program panics, the <code>-panic</code> flag controls what happens next:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">so run -panic<span class="o">=</span>trace .  <span class="c1"># print a stack trace, then exit(1) - the default</span>
</span></span><span class="line"><span class="cl">so run -panic<span class="o">=</span><span class="nb">exit</span>  .  <span class="c1"># just exit(1) after the message</span>
</span></span><span class="line"><span class="cl">so run -panic<span class="o">=</span>abort .  <span class="c1"># raise SIGABRT for a debugger or core dump</span>
</span></span></code></pre></div><p>Stack trace frames represent each function in the call chain:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">Work</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Work</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">res</span> <span class="o">:=</span> <span class="nf">Calc</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="nx">res</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Calc</span><span class="p">(</span><span class="nx">x</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">x</span> <span class="o">==</span> <span class="mi">42</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="s">&#34;can&#39;t handle 42&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><codapi-snippet sandbox="so" editor="basic" template="header.go" output>
</codapi-snippet>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">panic: can&#39;t handle 42
</span></span><span class="line"><span class="cl">/tmp/solod_build764532392/main.c:27 (func main_Calc)
</span></span><span class="line"><span class="cl">/tmp/solod_run1106942066(main_Calc+0x51)
</span></span><span class="line"><span class="cl">/tmp/solod_run1106942066(main_Work+0x12)
</span></span><span class="line"><span class="cl">/tmp/solod_run1106942066(main+0x9)
</span></span></code></pre></div><p>The same system handles assertions like slice bounds, index-out-of-range, <code>c.Assert</code>, and similar checks. Instead of calling C's <code>assert</code>, they panic in a way that respects the <code>-panic</code> flag.</p>
<p>There's also a new <code>-sanitize</code> flag that enables C sanitizers (<code>address</code> and <code>undefined</code> by default) to help you catch more issues during development:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">so run -sanitize -panic<span class="o">=</span>abort example/play
</span></span></code></pre></div><h2 id="tooling">Tooling</h2>
<p><strong>'so test' and 'so bench'</strong>. Solod now has built-in test and benchmark runners. <code>so test</code> finds <code>TestXxx(t *testing.T)</code> functions in a package's <code>test</code> subdirectory, creates a runner, transpiles it, and runs it. <code>so bench</code> does the same for <code>BenchmarkXxx(b *testing.B)</code>.</p>
<p>A typical package layout with tests and benchmarks looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">so/uuid
</span></span><span class="line"><span class="cl">├── bench
</span></span><span class="line"><span class="cl">│   ├── main.go
</span></span><span class="line"><span class="cl">│   └── uuid.go
</span></span><span class="line"><span class="cl">├── test
</span></span><span class="line"><span class="cl">│   ├── main.go
</span></span><span class="line"><span class="cl">│   └── uuid.go
</span></span><span class="line"><span class="cl">└── uuid.go
</span></span></code></pre></div><p>There's also a quick check for memory leaks: <code>t.Allocator()</code> gives you a tracking allocator (described in the 'Safety net' section above), and the test will fail if anything allocated with it isn't freed by the end of the test.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">=== RUN   TestAlloc
</span></span><span class="line"><span class="cl">    memory leak: 1 unfreed allocation(s), 16 byte(s)
</span></span><span class="line"><span class="cl">--- FAIL: TestAlloc
</span></span></code></pre></div><p><strong>Fuzzing</strong>. Since Solod is a strict subset of Go, any So package is also a valid Go package. This means you get Go's built-in fuzzer for free, making fuzz testing pretty easy. So's <code>encoding/json</code> package takes advantage of this by using Go's own <code>encoding/json</code> as an oracle, making sure that every JSON document accepted by So is also accepted by Go.</p>
<p><strong>Automatic linking</strong>. The new <code>so:link</code> directive lets a package specify which C library it needs, and <code>so build</code> gathers these libraries and passes them to the C compiler. The standard packages already use the new directive, so importing <code>so/math</code> links with <code>-lm</code>, and <code>so/sync</code> or <code>so/conc</code> links with <code>-lpthread</code> — you no longer have to set <code>LDFLAGS</code> manually.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>With v0.3, Solod reaches an important milestone: a program can now do multiple things at once. The JSON package gives programs a standard way to communicate, and the safety checks help prevent silent failures — both during development and in production.</p>
<p>There's still a lot to do, of course. In the next release, the standard library will keep growing, and the language and tooling will get better to make programming in So more convenient and safe.</p>
<p>If you're interested, take a look at So's <a href="https://github.com/solod-dev/solod#readme">readme</a> — it has everything you need to get started. Or <a href="https://codapi.org/so">try So online</a> without installing anything.</p>
<script defer src="/modules/codapi/snippet.js"></script>
]]></content:encoded></item><item><title>On interactive Go tours</title><link>https://antonz.org/on-go-tours/</link><pubDate>Sat, 11 Jul 2026 12:30:00 +0000</pubDate><guid>https://antonz.org/on-go-tours/</guid><description>Wrapping up the series.</description><content:encoded><![CDATA[<p>Over the past two years, I've published interactive tours for five Go releases, from 1.22 to 1.26.</p>
<p>I know some of you have read them, and I've received a lot of kind words from you (even some core Go team members reached out) — thank you so much for that!</p>
<blockquote>
<p>Tour history:
<a href="/go-1-22/">Go 1.22</a> •
<a href="/go-1-23/">1.23</a> •
<a href="/go-1-24/">1.24</a> •
<a href="/go-1-25/">1.25</a> •
<a href="/go-1-26/">1.26</a> +
<a href="/go-features/">Go features by version</a></p>
</blockquote>
<p>Unfortunately, at some point, writing these tours stopped being fun and started to feel like a part-time job. I'm not really excited about that, so I've decided to stop.</p>
<p>I still like Go (well, most of it). I read a lot of Go code, I write some Go code, and I write <a href="/solod/">Solod</a> code, which is also Go 🙂 (Solod is a systems language with Go syntax and a Go-like stdlib).</p>
<p>I'm still pretty close to the language and will probably continue to write about it.</p>
<p>But the interactive tours story is over.</p>
]]></content:encoded></item><item><title>Go-flavored concurrency in C</title><link>https://antonz.org/concurrency-in-c/</link><pubDate>Fri, 10 Jul 2026 12:00:00 +0000</pubDate><guid>https://antonz.org/concurrency-in-c/</guid><description>Worker pools, channels, and mutexes - backed by pthreads.</description><content:encoded><![CDATA[<p>Go's concurrency is one of the main reasons people like the language. You write <code>go f()</code>, send values through channels, and the runtime scheduler runs thousands of goroutines on just a few OS threads. It feels effortless.</p>
<p>None of that machinery exists in C. Which made me wonder: how close can you get to Go's concurrency model using only POSIX threads? Obviously, native OS threads can't match the efficiency of lightweight goroutines, but what is the actual cost, when does it become a problem, and is there any way to at least partially avoid it?</p>
<p>I ran into these questions while adding concurrency to <a href="/solod/">Solod</a> (So), a strict subset of Go that translates to plain C, with no runtime and no garbage collector. In the end, I came to the conclusion that you can do quite a lot with pthreads — as long as you're honest about the tradeoffs.</p>
<p>This post is about the POSIX threads-based concurrency model I chose, the benefits it offers, and its limitations.</p>
<p><a href="#mutexcond">Mutex/Cond</a> •
<a href="#atomics">Atomics</a> •
<a href="#worker-pool">Pool</a> •
<a href="#channel">Channel</a> •
<a href="#performance">Performance</a> •
<a href="#design-decisions">Design</a> •
<a href="#wrapping-up">Wrapping up</a></p>
<h2 id="mutexcond">Mutex/Cond</h2>
<p>Everything in So's concurrency stack is built on two basic POSIX primitives: the mutex and the condition variable. <code>sync.Mutex</code> is a thin wrapper around <code>pthread_mutex_t</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Extracted from So&#39;s stdlib source code.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">Mutex</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">mu</span> <span class="nx">pthread_mutex_t</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">m</span> <span class="o">*</span><span class="nx">Mutex</span><span class="p">)</span> <span class="nf">Lock</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">rc</span> <span class="o">:=</span> <span class="nf">pthread_mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">m</span><span class="p">.</span><span class="nx">mu</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">rc</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="s">&#34;sync: Mutex.Lock failed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Since So translates to C, this is basically a struct that holds a <code>pthread_mutex_t</code> and a function that calls <code>pthread_mutex_lock</code>. Here's the transpiler output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// The translated C code.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="n">sync_Mutex</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">pthread_mutex_t</span> <span class="n">mu</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">sync_Mutex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">sync_Mutex_Lock</span><span class="p">(</span><span class="n">sync_Mutex</span><span class="o">*</span> <span class="n">m</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">rc</span> <span class="o">=</span> <span class="nf">pthread_mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">m</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">rc</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">so_panic</span><span class="p">(</span><span class="s">&#34;sync: Mutex.Lock failed&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><blockquote>
<p>That is the whole translation — the generated C is a near-mechanical mirror of the So code, only noisier. From here on, I'll mainly show the So version, but I'll also provide the C code for those who are interested.</p>
</blockquote>
<p>There's nothing exciting here: <code>sync.Mutex</code> is a pthread mutex wrapper that panics if something goes wrong (which is rare).</p>
<p>The companion primitive is <code>sync.Cond</code>, a wrapper around <code>pthread_cond_t</code>. It's the standard &quot;wait until a condition holds&quot; tool, associated with a mutex:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Cond</span> <span class="kd">struct</span>           <span class="c1">// wraps pthread_cond_t + pthread_mutex_t
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="o">*</span><span class="nx">Cond</span><span class="p">)</span> <span class="nf">Wait</span><span class="p">()</span>      <span class="c1">// wraps pthread_cond_wait
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="o">*</span><span class="nx">Cond</span><span class="p">)</span> <span class="nf">Signal</span><span class="p">()</span>    <span class="c1">// wraps pthread_cond_signal
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="o">*</span><span class="nx">Cond</span><span class="p">)</span> <span class="nf">Broadcast</span><span class="p">()</span> <span class="c1">// wraps pthread_cond_broadcast
</span></span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">sync_Cond</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">pthread_cond_t</span> <span class="n">cond</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sync_Mutex</span><span class="o">*</span>    <span class="n">mu</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">sync_Cond</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">sync_Cond_Wait</span><span class="p">(</span><span class="n">sync_Cond</span><span class="o">*</span> <span class="n">c</span><span class="p">);</span>      <span class="c1">// wraps pthread_cond_wait
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="nf">sync_Cond_Signal</span><span class="p">(</span><span class="n">sync_Cond</span><span class="o">*</span> <span class="n">c</span><span class="p">);</span>    <span class="c1">// wraps pthread_cond_signal
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="nf">sync_Cond_Broadcast</span><span class="p">(</span><span class="n">sync_Cond</span><span class="o">*</span> <span class="n">c</span><span class="p">);</span> <span class="c1">// wraps pthread_cond_broadcast
</span></span></span></code></pre></div></details>
<p>These two types — <code>Mutex</code> and <code>Cond</code> — are the foundation. Other concurrency tools — <code>Once</code>, the thread pool, channels — are built using a mutex and one or more condition variables. This has several effects on performance, as we'll see later.</p>
<h2 id="atomics">Atomics</h2>
<p>Not everything needs a lock. So's <code>sync/atomic</code> mirrors Go's: <code>Bool</code>, <code>Int32</code>, <code>Int64</code>, <code>Uint32</code>, <code>Uint64</code>, and a generic <code>Pointer[T]</code>, all with <code>Load</code>, <code>Store</code>, <code>Swap</code>, and <code>CompareAndSwap</code> methods.</p>
<p>The nice thing is that these don't need pthreads at all. They map directly to the C compiler's <code>__atomic</code> builtins — the same hardware instructions that Go's compiler emits. So there's no reason for them to be any slower, and they're not:</p>
<table>
<thead>
<tr>
<th>Atomic op</th>
<th style="text-align:right">Go</th>
<th style="text-align:right">So</th>
<th>Winner</th>
</tr>
</thead>
<tbody>
<tr>
<td>Load</td>
<td style="text-align:right">2ns</td>
<td style="text-align:right">2ns</td>
<td>~same</td>
</tr>
<tr>
<td>Store</td>
<td style="text-align:right">2ns</td>
<td style="text-align:right">2ns</td>
<td>~same</td>
</tr>
<tr>
<td>CompareAndSwap</td>
<td style="text-align:right">13ns</td>
<td style="text-align:right">13ns</td>
<td>~same</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Each number is the cost of one operation on a single thread.</p>
</blockquote>
<p><code>sync.Once</code> is a good example of using atomics effectively. Its fast path only needs a single atomic load — after the given function runs, every future call to <code>Do</code> checks a flag and returns:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Once</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">mu</span>   <span class="nx">Mutex</span>
</span></span><span class="line"><span class="cl">    <span class="nx">done</span> <span class="nx">atomic</span><span class="p">.</span><span class="nx">Bool</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Do calls f if and only if Do is being called
</span></span></span><span class="line"><span class="cl"><span class="c1">// for the first time for this o.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">o</span> <span class="o">*</span><span class="nx">Once</span><span class="p">)</span> <span class="nf">Do</span><span class="p">(</span><span class="nx">f</span> <span class="kd">func</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">o</span><span class="p">.</span><span class="nx">done</span><span class="p">.</span><span class="nf">Load</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// lock-free fast path
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">return</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// slow path...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">sync_Once</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sync_Mutex</span> <span class="n">mu</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">atomic_Bool</span> <span class="n">done</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">sync_Once</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Do calls f if and only if Do is being called
</span></span></span><span class="line"><span class="cl"><span class="c1">// for the first time for this o.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="nf">sync_Once_Do</span><span class="p">(</span><span class="n">sync_Once</span><span class="o">*</span> <span class="n">o</span><span class="p">,</span> <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">f</span><span class="p">)())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">atomic_Bool_Load</span><span class="p">(</span><span class="o">&amp;</span><span class="n">o</span><span class="o">-&gt;</span><span class="n">done</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// lock-free fast path
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// slow path...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div></details>
<h2 id="worker-pool">Worker pool</h2>
<p>To actually run code concurrently, you need threads. The <code>conc.Thread</code> type wraps <code>pthread_t</code> and its related functions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Thread</span> <span class="kd">struct</span>          <span class="c1">// wraps pthread_t
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">th</span> <span class="nx">Thread</span><span class="p">)</span> <span class="nf">Wait</span><span class="p">()</span> <span class="nx">any</span> <span class="c1">// wraps pthread_join
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">th</span> <span class="nx">Thread</span><span class="p">)</span> <span class="nf">Detach</span><span class="p">()</span>   <span class="c1">// wraps pthread_detach
</span></span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">conc_Thread</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">pthread_t</span> <span class="n">t</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">conc_Thread</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span><span class="o">*</span> <span class="nf">conc_Thread_Wait</span><span class="p">(</span><span class="n">conc_Thread</span> <span class="n">th</span><span class="p">);</span>   <span class="c1">// wraps pthread_join
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">void</span>  <span class="nf">conc_Thread_Detach</span><span class="p">(</span><span class="n">conc_Thread</span> <span class="n">th</span><span class="p">);</span> <span class="c1">// wraps pthread_detach
</span></span></span></code></pre></div></details>
<p>Consider this <code>conc.Go</code> function:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Go launches an OS thread that runs fn(arg) and returns a handle to it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Go</span><span class="p">(</span><span class="nx">entry</span> <span class="kd">func</span><span class="p">(</span><span class="nx">any</span><span class="p">)</span> <span class="nx">any</span><span class="p">,</span> <span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span> <span class="nx">Thread</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">th</span> <span class="nx">Thread</span>
</span></span><span class="line"><span class="cl">    <span class="nx">rc</span> <span class="o">:=</span> <span class="nf">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">th</span><span class="p">.</span><span class="nx">t</span><span class="p">,</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">entry</span><span class="p">,</span> <span class="nx">arg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Go launches an OS thread that runs fn(arg) and returns a handle to it.
</span></span></span><span class="line"><span class="cl"><span class="c1">// `any` in So translates to `void*` in C.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">conc_Thread</span> <span class="nf">conc_Go</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="p">(</span><span class="o">*</span><span class="n">entry</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span><span class="p">),</span> <span class="kt">void</span><span class="o">*</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">conc_Thread</span> <span class="n">th</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">rc</span> <span class="o">=</span> <span class="nf">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">th</span><span class="p">.</span><span class="n">t</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">entry</span><span class="p">,</span> <span class="n">arg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div></details>
<p>Usage example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">work</span><span class="p">(</span><span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span> <span class="nx">any</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">acc</span> <span class="o">:=</span> <span class="nx">arg</span><span class="p">.(</span><span class="o">*</span><span class="nx">Account</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">acc</span> <span class="nx">Account</span>
</span></span><span class="line"><span class="cl">    <span class="nx">th</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="nx">work</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">acc</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ... do other work concurrently ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">th</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span> <span class="c1">// work is complete once Wait returns
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">void</span><span class="o">*</span> <span class="nf">work</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">main_Account</span><span class="o">*</span> <span class="n">acc</span> <span class="o">=</span> <span class="p">(</span><span class="n">main_Account</span><span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">main_Account</span> <span class="n">acc</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">conc_Thread</span> <span class="n">th</span> <span class="o">=</span> <span class="nf">conc_Go</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">acc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ... do other work concurrently ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">conc_Thread_Wait</span><span class="p">(</span><span class="n">th</span><span class="p">);</span> <span class="c1">// work is complete once Wait returns
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div></details>
<p>It might look like <code>go work(&amp;acc)</code>, but that's just on the surface. <code>conc.Go</code> starts an actual OS thread, not a goroutine. You have to eventually call <code>Wait</code> to join or <code>Detach</code> it, or else its resources will leak. Also, OS threads are expensive to create — they're nothing like Go's goroutines, which only need a few kilobytes of stack and start up in nanoseconds.</p>
<p>That's exactly why you usually don't want to call <code>Go</code> inside a loop. For tasks that are short-lived or happen often, it's better to use a pool of long-lived worker threads and send tasks to them.</p>
<p><code>conc.Pool</code> to the rescue:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">     Worker thread pool in So
</span></span><span class="line"><span class="cl">┌────────┐ ┌────────┐   ┌────────┐
</span></span><span class="line"><span class="cl">│ Task 1 │ │ Task 2 │...│ Task M │  M tasks
</span></span><span class="line"><span class="cl">└────────┘ └────────┘   └────────┘
</span></span><span class="line"><span class="cl">┌────────────────────────────────┐
</span></span><span class="line"><span class="cl">│           conc.Pool            │  coordinator
</span></span><span class="line"><span class="cl">└────────────────────────────────┘
</span></span><span class="line"><span class="cl">┌────────┐ ┌────────┐   ┌────────┐
</span></span><span class="line"><span class="cl">│ Thrd 1 │ │ Thrd 2 │...│ Thrd N │  N threads, N &lt;&lt; M
</span></span><span class="line"><span class="cl">└────────┘ └────────┘   └────────┘
</span></span><span class="line"><span class="cl">┌────────────────────────────────┐
</span></span><span class="line"><span class="cl">│          OS scheduler          │
</span></span><span class="line"><span class="cl">└────────────────────────────────┘
</span></span></code></pre></div><p>Usage example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Task</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">in</span>  <span class="kt">int</span>
</span></span><span class="line"><span class="cl">    <span class="nx">out</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">square</span><span class="p">(</span><span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">task</span> <span class="o">:=</span> <span class="nx">arg</span><span class="p">.(</span><span class="o">*</span><span class="nx">Task</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">task</span><span class="p">.</span><span class="nx">out</span> <span class="p">=</span> <span class="nx">task</span><span class="p">.</span><span class="nx">in</span> <span class="o">*</span> <span class="nx">task</span><span class="p">.</span><span class="nx">in</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">tasks</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="nx">Task</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">opts</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nx">PoolOpts</span><span class="p">{</span><span class="nx">NumThreads</span><span class="p">:</span> <span class="mi">2</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">pool</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nf">NewPool</span><span class="p">(</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="nx">opts</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nx">pool</span><span class="p">.</span><span class="nf">Free</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">tasks</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">tasks</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">in</span> <span class="p">=</span> <span class="nx">i</span>
</span></span><span class="line"><span class="cl">        <span class="nx">pool</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="nx">square</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">tasks</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">pool</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">main_Task</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">in</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">out</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">main_Task</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">square</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">main_Task</span><span class="o">*</span> <span class="n">task</span> <span class="o">=</span> <span class="p">(</span><span class="n">main_Task</span><span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">task</span><span class="o">-&gt;</span><span class="n">out</span> <span class="o">=</span> <span class="n">task</span><span class="o">-&gt;</span><span class="n">in</span> <span class="o">*</span> <span class="n">task</span><span class="o">-&gt;</span><span class="n">in</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">tasks</span> <span class="o">=</span> <span class="nf">so_make_slice</span><span class="p">(</span><span class="n">main_Task</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">conc_PoolOpts</span> <span class="n">opts</span> <span class="o">=</span> <span class="p">(</span><span class="n">conc_PoolOpts</span><span class="p">){.</span><span class="n">NumThreads</span> <span class="o">=</span> <span class="mi">2</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">conc_Pool</span><span class="o">*</span> <span class="n">pool</span> <span class="o">=</span> <span class="nf">conc_NewPool</span><span class="p">(</span><span class="n">mem_System</span><span class="p">,</span> <span class="n">opts</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="n">so_int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="nf">so_len</span><span class="p">(</span><span class="n">tasks</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// so_at is a generic macro to get the i-th element of a
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// specific type (main_Task here) from a type-erased slice.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// Here we&#39;re getting the i-th task from the tasks slice.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">so_at</span><span class="p">(</span><span class="n">main_Task</span><span class="p">,</span> <span class="n">tasks</span><span class="p">,</span> <span class="n">i</span><span class="p">).</span><span class="n">in</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">conc_Pool_Go</span><span class="p">(</span><span class="n">pool</span><span class="p">,</span> <span class="n">square</span><span class="p">,</span> <span class="o">&amp;</span><span class="nf">so_at</span><span class="p">(</span><span class="n">main_Task</span><span class="p">,</span> <span class="n">tasks</span><span class="p">,</span> <span class="n">i</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nf">conc_Pool_Wait</span><span class="p">(</span><span class="n">pool</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">conc_Pool_Free</span><span class="p">(</span><span class="n">pool</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></details>
<blockquote>
<p>The first argument to <code>NewPool</code>, <code>mem.System</code>, is a memory allocator. Solod avoids hidden allocations, so anything that needs memory takes an allocator explicitly — here it backs the pool's task queue.</p>
</blockquote>
<p>Under the hood, a <code>Pool</code> is a fixed group of worker threads that pull tasks from a shared queue (a ring buffer). It uses one mutex and a few condition variables:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Pool is a bounded pool of worker threads with a wait queue
</span></span></span><span class="line"><span class="cl"><span class="c1">// which execute tasks of the form func(any).
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">Pool</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">alloc</span> <span class="nx">mem</span><span class="p">.</span><span class="nx">Allocator</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">mu</span>       <span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span>
</span></span><span class="line"><span class="cl">    <span class="nx">notEmpty</span> <span class="nx">sync</span><span class="p">.</span><span class="nx">Cond</span> <span class="c1">// signaled when a task is enqueued
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">notFull</span>  <span class="nx">sync</span><span class="p">.</span><span class="nx">Cond</span> <span class="c1">// signaled when a slot frees
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">allDone</span>  <span class="nx">sync</span><span class="p">.</span><span class="nx">Cond</span> <span class="c1">// broadcast when no task is in flight
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="nx">workers</span> <span class="p">[]</span><span class="nx">Thread</span>
</span></span><span class="line"><span class="cl">    <span class="nx">queue</span>   <span class="p">[]</span><span class="nx">task</span> <span class="c1">// ring buffer of submitted tasks
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">active</span>  <span class="kt">int</span>    <span class="c1">// tasks submitted but not yet finished
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">stopped</span> <span class="kt">bool</span>   <span class="c1">// set by Free to drain and exit
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// NewPool creates a pool with a given number
</span></span></span><span class="line"><span class="cl"><span class="c1">// of worker threads and starts them.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">NewPool</span><span class="p">(</span><span class="nx">alloc</span> <span class="nx">mem</span><span class="p">.</span><span class="nx">Allocator</span><span class="p">,</span> <span class="nx">opts</span> <span class="nx">PoolOpts</span><span class="p">)</span> <span class="o">*</span><span class="nx">Pool</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Go submits a task for execution, blocking while the queue is full.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">p</span> <span class="o">*</span><span class="nx">Pool</span><span class="p">)</span> <span class="nf">Go</span><span class="p">(</span><span class="nx">fn</span> <span class="kd">func</span><span class="p">(</span><span class="nx">any</span><span class="p">),</span> <span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Wait blocks until all submitted tasks finish.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">p</span> <span class="o">*</span><span class="nx">Pool</span><span class="p">)</span> <span class="nf">Wait</span><span class="p">()</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Pool is a bounded pool of worker threads with a wait queue
</span></span></span><span class="line"><span class="cl"><span class="c1">// which execute tasks of the form func(any).
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="n">conc_Pool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Allocator</span> <span class="n">alloc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">sync_Mutex</span> <span class="n">mu</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sync_Cond</span>  <span class="n">notEmpty</span><span class="p">;</span> <span class="c1">// signaled when a task is enqueued
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">sync_Cond</span>  <span class="n">notFull</span><span class="p">;</span>  <span class="c1">// signaled when a slot frees
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">sync_Cond</span>  <span class="n">allDone</span><span class="p">;</span>  <span class="c1">// broadcast when no task is in flight
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">workers</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">queue</span><span class="p">;</span>      <span class="c1">// ring buffer of submitted tasks
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_int</span>   <span class="n">active</span><span class="p">;</span>     <span class="c1">// tasks submitted but not yet finished
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">bool</span>     <span class="n">stopped</span><span class="p">;</span>    <span class="c1">// set by Free to drain and exit
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> <span class="n">conc_Pool</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">conc_Pool</span><span class="o">*</span> <span class="nf">conc_NewPool</span><span class="p">(</span><span class="n">mem_Allocator</span> <span class="n">alloc</span><span class="p">,</span> <span class="n">conc_PoolOpts</span> <span class="n">opts</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">void</span>       <span class="nf">conc_Pool_Go</span><span class="p">(</span><span class="n">conc_Pool</span><span class="o">*</span> <span class="n">p</span><span class="p">,</span> <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">fn</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span><span class="p">),</span> <span class="kt">void</span><span class="o">*</span> <span class="n">arg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">void</span>       <span class="nf">conc_Pool_Wait</span><span class="p">(</span><span class="n">conc_Pool</span><span class="o">*</span> <span class="n">p</span><span class="p">);</span>
</span></span></code></pre></div></details>
<p><code>notEmpty</code> wakes up a worker when there are tasks to do, <code>notFull</code> applies back-pressure when the queue is full, and <code>allDone</code> lets <code>Wait</code> know when everything is finished. It's a classic producer-consumer setup, about <a href="https://github.com/solod-dev/solod/blob/main/so/conc/pool.go">200 lines of code</a>, and there's nothing fancy about it.</p>
<p>The heart of the pool is the worker loop. Each thread blocks until a task appears, runs it outside the lock so workers execute in parallel, then records that it finished:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// workerMain runs on every pool thread: pull a task, run it, repeat.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">workerMain</span><span class="p">(</span><span class="nx">arg</span> <span class="nx">any</span><span class="p">)</span> <span class="nx">any</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">p</span> <span class="o">:=</span> <span class="nx">arg</span><span class="p">.(</span><span class="o">*</span><span class="nx">Pool</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">p</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="nx">p</span><span class="p">.</span><span class="nf">qempty</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="p">!</span><span class="nx">p</span><span class="p">.</span><span class="nx">stopped</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">p</span><span class="p">.</span><span class="nx">notEmpty</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span> <span class="c1">// sleep until a task is enqueued
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nx">p</span><span class="p">.</span><span class="nf">qempty</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="nx">p</span><span class="p">.</span><span class="nx">stopped</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">p</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span> <span class="c1">// queue drained and pool shutting down
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nx">t</span> <span class="o">:=</span> <span class="nx">p</span><span class="p">.</span><span class="nf">qpop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="nx">p</span><span class="p">.</span><span class="nx">notFull</span><span class="p">.</span><span class="nf">Signal</span><span class="p">()</span> <span class="c1">// a slot freed for a waiting submitter
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nx">p</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nx">t</span><span class="p">.</span><span class="nf">fn</span><span class="p">(</span><span class="nx">t</span><span class="p">.</span><span class="nx">arg</span><span class="p">)</span> <span class="c1">// run the task with the lock released
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">        <span class="nx">p</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="nx">p</span><span class="p">.</span><span class="nx">active</span><span class="o">--</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nx">p</span><span class="p">.</span><span class="nx">active</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">p</span><span class="p">.</span><span class="nx">allDone</span><span class="p">.</span><span class="nf">Broadcast</span><span class="p">()</span> <span class="c1">// wake anyone parked in Wait
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nx">p</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// workerMain runs on every pool thread: pull a task, run it, repeat.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">static</span> <span class="kt">void</span><span class="o">*</span> <span class="nf">workerMain</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">conc_Pool</span><span class="o">*</span> <span class="n">p</span> <span class="o">=</span> <span class="p">(</span><span class="n">conc_Pool</span><span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sync_Mutex_Lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(;</span> <span class="nf">conc_Pool_qempty</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">stopped</span><span class="p">;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">sync_Cond_Wait</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">notEmpty</span><span class="p">);</span> <span class="c1">// sleep until a task is enqueued
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nf">conc_Pool_qempty</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">p</span><span class="o">-&gt;</span><span class="n">stopped</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">sync_Mutex_Unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span> <span class="c1">// queue drained and pool shutting down
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">task</span> <span class="n">t</span> <span class="o">=</span> <span class="nf">conc_Pool_qpop</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sync_Cond_Signal</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">notFull</span><span class="p">);</span> <span class="c1">// a slot freed for a waiting submitter
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">sync_Mutex_Unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">t</span><span class="p">.</span><span class="nf">fn</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">arg</span><span class="p">);</span> <span class="c1">// run the task with the lock released
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">        <span class="nf">sync_Mutex_Lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">p</span><span class="o">-&gt;</span><span class="n">active</span><span class="o">--</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">active</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">sync_Cond_Broadcast</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">allDone</span><span class="p">);</span> <span class="c1">// wake anyone parked in Wait
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sync_Mutex_Unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></details>
<p>This is what separates a pool from a plain queue. <code>Pool.Go</code> bumps <code>active</code> as it enqueues; each worker decrements it after running a task, and the last one out broadcasts <code>allDone</code>.</p>
<p><code>Pool.Wait</code> sleeps until the count hits zero:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Wait blocks until every submitted task has finished.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">p</span> <span class="o">*</span><span class="nx">Pool</span><span class="p">)</span> <span class="nf">Wait</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">p</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">p</span><span class="p">.</span><span class="nx">active</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">p</span><span class="p">.</span><span class="nx">allDone</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">p</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Wait blocks until every submitted task has finished.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="nf">conc_Pool_Wait</span><span class="p">(</span><span class="n">conc_Pool</span><span class="o">*</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sync_Mutex_Lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;</span> <span class="n">p</span><span class="o">-&gt;</span><span class="n">active</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sync_Cond_Wait</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">allDone</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sync_Mutex_Unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></details>
<p>The tradeoff is that the number of worker threads is fixed. In Go, a program can handle thousands of concurrent I/O waits because blocked goroutines use very little memory. A So pool can't do this — if all N workers are parked on a blocking syscall, the pool is stalled until one returns. You have to set the pool size based on the workload, instead of letting the runtime manage it for you.</p>
<h2 id="channel">Channel</h2>
<p>Channels are an important part of Go's concurrency model, and So's <code>conc.Chan[T]</code> gives you something quite similar. Just like in Go, it passes values by copy and comes in buffered and unbuffered flavors:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nx">NewChan</span><span class="p">[</span><span class="kt">int</span><span class="p">](</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1">// buffered, capacity 2
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">defer</span> <span class="nx">ch</span><span class="p">.</span><span class="nf">Free</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Producer on its own thread.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">prod</span> <span class="o">:=</span> <span class="nx">producer</span><span class="p">{</span><span class="nx">ch</span><span class="p">:</span> <span class="o">&amp;</span><span class="nx">ch</span><span class="p">,</span> <span class="nx">n</span><span class="p">:</span> <span class="mi">5</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">thr</span> <span class="o">:=</span> <span class="nx">conc</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="nx">produce</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">prod</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">defer</span> <span class="nx">thr</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Consume until the channel is closed and drained.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="nx">v</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="nx">ch</span><span class="p">.</span><span class="nf">Recv</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">v</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;received %d\n&#34;</span><span class="p">,</span> <span class="nx">v</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// conc_NewChan, conc_Chan_Recv, and friends are generic macros:
</span></span></span><span class="line"><span class="cl"><span class="c1">// the element type (so_int here) is passed as the first argument.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">conc_Chan</span> <span class="n">ch</span> <span class="o">=</span> <span class="nf">conc_NewChan</span><span class="p">(</span><span class="n">so_int</span><span class="p">,</span> <span class="n">mem_System</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span> <span class="c1">// buffered, capacity 2
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// Producer on its own thread.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">producer</span> <span class="n">prod</span> <span class="o">=</span> <span class="p">(</span><span class="n">producer</span><span class="p">){.</span><span class="n">ch</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">ch</span><span class="p">,</span> <span class="p">.</span><span class="n">n</span> <span class="o">=</span> <span class="mi">5</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="n">conc_Thread</span> <span class="n">thr</span> <span class="o">=</span> <span class="nf">conc_Go</span><span class="p">(</span><span class="n">produce</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">prod</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Consume until the channel is closed and drained.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">so_int</span> <span class="n">v</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(;</span> <span class="nf">conc_Chan_Recv</span><span class="p">(</span><span class="n">so_int</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ch</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">v</span><span class="p">);)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">fmt_Printf</span><span class="p">(</span><span class="s">&#34;received %d</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">v</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">conc_Thread_Wait</span><span class="p">(</span><span class="n">thr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">conc_Chan_Free</span><span class="p">(</span><span class="n">so_int</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ch</span><span class="p">);</span>
</span></span></code></pre></div></details>
<p><code>Chan[T]</code> is a thin generic shell over one of two engines, picked at creation time:</p>
<p><strong>Buffered</strong> (<code>n &gt; 0</code>) is a mutex-guarded ring buffer with <code>notEmpty</code> and <code>notFull</code> condition variables — like the <code>Pool</code> queue. Senders block when it's full, receivers block when it's empty.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Buffer</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">alloc</span> <span class="nx">mem</span><span class="p">.</span><span class="nx">Allocator</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">mu</span>       <span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span>
</span></span><span class="line"><span class="cl">    <span class="nx">notEmpty</span> <span class="nx">sync</span><span class="p">.</span><span class="nx">Cond</span> <span class="c1">// signaled when an item becomes available
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">notFull</span>  <span class="nx">sync</span><span class="p">.</span><span class="nx">Cond</span> <span class="c1">// signaled when a slot frees
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="nx">buf</span>    <span class="nx">mem</span><span class="p">.</span><span class="nx">Array</span>   <span class="c1">// ring buffer
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">closed</span> <span class="kt">bool</span>        <span class="c1">// true after Close
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Send copies v into the ring, blocking while it is full.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">ch</span> <span class="o">*</span><span class="nx">Buffer</span><span class="p">)</span> <span class="nf">Send</span><span class="p">(</span><span class="nx">v</span> <span class="nx">any</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ch</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">ch</span><span class="p">.</span><span class="nf">bfull</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">ch</span><span class="p">.</span><span class="nx">notFull</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span> <span class="c1">// back-pressure until a slot frees
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ch</span><span class="p">.</span><span class="nf">bpush</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ch</span><span class="p">.</span><span class="nx">notEmpty</span><span class="p">.</span><span class="nf">Signal</span><span class="p">()</span> <span class="c1">// wake one waiting receiver
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">ch</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">conc_Buffer</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Allocator</span> <span class="n">alloc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">sync_Mutex</span> <span class="n">mu</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sync_Cond</span>  <span class="n">notEmpty</span><span class="p">;</span> <span class="c1">// signaled when an item becomes available
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">sync_Cond</span>  <span class="n">notFull</span><span class="p">;</span>  <span class="c1">// signaled when a slot frees
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Array</span> <span class="n">buf</span><span class="p">;</span>       <span class="c1">// ring buffer
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">bool</span> <span class="n">closed</span><span class="p">;</span>         <span class="c1">// true after Close
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> <span class="n">conc_Buffer</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Send copies v into the ring, blocking while it is full.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="nf">conc_Buffer_Send</span><span class="p">(</span><span class="n">conc_Buffer</span><span class="o">*</span> <span class="n">ch</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">v</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sync_Mutex_Lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;</span> <span class="nf">conc_Buffer_bfull</span><span class="p">(</span><span class="n">ch</span><span class="p">);)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sync_Cond_Wait</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">notFull</span><span class="p">);</span> <span class="c1">// back-pressure until a slot frees
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nf">conc_Buffer_bpush</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span> <span class="n">v</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sync_Cond_Signal</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">notEmpty</span><span class="p">);</span> <span class="c1">// wake one waiting receiver
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nf">sync_Mutex_Unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></details>
<blockquote>
<p>The full implementation also checks for <code>closed</code>, but I left it out for brevity.</p>
</blockquote>
<p><code>Recv</code> is the mirror method: block while empty, pop the next value, signal <code>notFull</code> to wake a sender. It also handles the closed channel, returning <code>false</code> once the buffer is closed and drained. The rest is this lock-wait-signal core.</p>
<p><a href="https://github.com/solod-dev/solod/blob/main/so/conc/buffer.go">Buffer source code</a></p>
<p><strong>Unbuffered</strong> (<code>n == 0</code>) is a rendezvous: each send blocks until a receiver takes the value, copying <code>vsize</code> bytes directly from the sender's stack to the receiver's destination without using an intermediate buffer.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Rendezvous</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">alloc</span> <span class="nx">mem</span><span class="p">.</span><span class="nx">Allocator</span>
</span></span><span class="line"><span class="cl">    <span class="nx">vsize</span> <span class="kt">int</span> <span class="c1">// size in bytes of a handed-off value
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="nx">mu</span>   <span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cond</span> <span class="nx">sync</span><span class="p">.</span><span class="nx">Cond</span> <span class="c1">// broadcast on every slot state change
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="nx">src</span>     <span class="nx">any</span>  <span class="c1">// the sender&#39;s published value (valid while full)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">full</span>    <span class="kt">bool</span> <span class="c1">// a value is published and not yet freed
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">claimed</span> <span class="kt">bool</span> <span class="c1">// the published value has been taken by a receiver
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">closed</span>  <span class="kt">bool</span> <span class="c1">// true after Close
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Send publishes v and waits for a receiver to take it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">ch</span> <span class="o">*</span><span class="nx">Rendezvous</span><span class="p">)</span> <span class="nf">Send</span><span class="p">(</span><span class="nx">v</span> <span class="nx">any</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ch</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">ch</span><span class="p">.</span><span class="nx">full</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">ch</span><span class="p">.</span><span class="nx">cond</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>  <span class="c1">// wait for the previous hand-off to finish
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ch</span><span class="p">.</span><span class="nx">src</span><span class="p">,</span> <span class="nx">ch</span><span class="p">.</span><span class="nx">full</span><span class="p">,</span> <span class="nx">ch</span><span class="p">.</span><span class="nx">claimed</span> <span class="p">=</span> <span class="nx">v</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">false</span> <span class="c1">// publish
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">ch</span><span class="p">.</span><span class="nx">cond</span><span class="p">.</span><span class="nf">Broadcast</span><span class="p">()</span> <span class="c1">// wakeup #1: wake a receiver
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="p">!</span><span class="nx">ch</span><span class="p">.</span><span class="nx">claimed</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">ch</span><span class="p">.</span><span class="nx">cond</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>  <span class="c1">// wait until the value is taken
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ch</span><span class="p">.</span><span class="nx">src</span><span class="p">,</span> <span class="nx">ch</span><span class="p">.</span><span class="nx">full</span> <span class="p">=</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">false</span> <span class="c1">// free the slot
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">ch</span><span class="p">.</span><span class="nx">cond</span><span class="p">.</span><span class="nf">Broadcast</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ch</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details>
    <summary>Show the translated C code</summary>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">conc_Rendezvous</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Allocator</span> <span class="n">alloc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">vsize</span><span class="p">;</span> <span class="c1">// size in bytes of a handed-off value
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="n">sync_Mutex</span> <span class="n">mu</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sync_Cond</span>  <span class="n">cond</span><span class="p">;</span> <span class="c1">// broadcast on every slot state change
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">src</span><span class="p">;</span>     <span class="c1">// the sender&#39;s published value (valid while full)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">bool</span>  <span class="n">full</span><span class="p">;</span>    <span class="c1">// a value is published and not yet freed
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">bool</span>  <span class="n">claimed</span><span class="p">;</span> <span class="c1">// the published value has been taken by a receiver
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">bool</span>  <span class="n">closed</span><span class="p">;</span>  <span class="c1">// true after Close
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> <span class="n">conc_Rendezvous</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Send publishes v and waits for a receiver to take it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="nf">conc_Rendezvous_Send</span><span class="p">(</span><span class="n">conc_Rendezvous</span><span class="o">*</span> <span class="n">ch</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">v</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sync_Mutex_Lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;</span> <span class="n">ch</span><span class="o">-&gt;</span><span class="n">full</span><span class="p">;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sync_Cond_Wait</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">cond</span><span class="p">);</span>  <span class="c1">// wait for the previous hand-off to finish
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">ch</span><span class="o">-&gt;</span><span class="n">src</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span>                    <span class="c1">// publish
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">ch</span><span class="o">-&gt;</span><span class="n">full</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">ch</span><span class="o">-&gt;</span><span class="n">claimed</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sync_Cond_Broadcast</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">cond</span><span class="p">);</span> <span class="c1">// wakeup #1: wake a receiver
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="p">(;</span> <span class="o">!</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">claimed</span><span class="p">;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sync_Cond_Wait</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">cond</span><span class="p">);</span>  <span class="c1">// wait until the value is taken
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">ch</span><span class="o">-&gt;</span><span class="n">full</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>               <span class="c1">// free the slot
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">ch</span><span class="o">-&gt;</span><span class="n">src</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sync_Cond_Broadcast</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">cond</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sync_Mutex_Unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ch</span><span class="o">-&gt;</span><span class="n">mu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></details>
<p><code>Recv</code> is the other half: it waits for a published, unclaimed value, copies <code>vsize</code> bytes straight from the sender's stack into <code>dst</code> (no intermediate buffer), marks it as claimed, and broadcasts to wake the sender back, creating wakeup #2. One hand-off, two wakeups.</p>
<p>Copying directly from the sender's stack is safe because of that second wakeup. <code>src</code> is a pointer to <code>v</code>, which lives on the sender's stack. While the receiver is reading it, the sender is parked in <code>for !ch.claimed { ch.cond.Wait() }</code>, so its stack frame stays alive. The sender only returns (and reclaims that memory) after the receiver sets <code>claimed</code> and wakes it up. There's no need to copy into a shared buffer because the source is guaranteed to outlive the read.</p>
<p><a href="https://github.com/solod-dev/solod/blob/main/so/conc/rendezvous.go">Rendezvous source code</a></p>
<p>As you can see, the API is pretty similar to Go. Now let's look at the numbers.</p>
<h2 id="performance">Performance</h2>
<p>Here's the main tradeoff: pthread-based concurrency primitives are fast when no one has to block, but they get slow when someone does. And it's always for the same reason.</p>
<p>Go schedules goroutines in userspace. When one goroutine blocks on a channel and another wakes it up, the runtime moves them between its own queues — no kernel involved. POSIX threads, on the other hand, don't provide a userland scheduler. When a thread blocks on a condition variable, it parks in the kernel, and waking it up requires a syscall. Every hand-off between threads that actually parks pays the cost of a syscall on both ends.</p>
<p>You can clearly see the difference in the mutex benchmarks. With 8 competing threads, it all comes down to whether the waiting threads have to park or not:</p>
<table>
<thead>
<tr>
<th>Mutex benchmark</th>
<th style="text-align:right">Go</th>
<th style="text-align:right">So</th>
<th>Winner</th>
</tr>
</thead>
<tbody>
<tr>
<td>Uncontended, 1 thread</td>
<td style="text-align:right">14ns</td>
<td style="text-align:right">9ns</td>
<td>So - 1.6x</td>
</tr>
<tr>
<td>Contended spin, 8 threads</td>
<td style="text-align:right">75ns</td>
<td style="text-align:right">27ns</td>
<td>So - 2.8x</td>
</tr>
<tr>
<td>Contended work, 8 threads</td>
<td style="text-align:right">1.1µs</td>
<td style="text-align:right">2.0µs</td>
<td>Go - 1.8x</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Each number is the average time for a single <code>Lock</code>/<code>Unlock</code> pair. The uncontended benchmark runs on one thread, while the contended benchmarks have multiple threads fighting over the same mutex.</p>
</blockquote>
<p>Notice that So actually wins the first two benchmarks, and for good reason. So's <code>Lock</code> is a plain <code>pthread_mutex_lock</code> call with nothing extra, while Go's <code>sync.Mutex</code> adds more overhead — like starvation-mode tracking and a runtime that stays involved because a goroutine can be preempted in the middle of a critical section.</p>
<p>When nobody parks, that overhead is the main cost, and the thinner wrapper is closer to the hardware. With an empty critical section (the <em>spin</em> benchmark), a waiting thread grabs the lock while still spinning and almost never parks — So wins by 2.8x. The uncontended benchmark (a single thread, no contention) shows the same thing: less code between the call and the lock, so 9ns versus 14ns.</p>
<p>The picture flips the moment threads have to park. Give the critical section about a microsecond of real work (the <em>work</em> benchmark) and waiters exhaust their spin budget and park. Now every hand-off costs a wakeup syscall, and So drops to half of Go's throughput. The work is identical in both cases — the difference comes from the parking cost.</p>
<p>Condition variables demonstrate this clearly because they <em>always</em> park:</p>
<table>
<thead>
<tr>
<th>Cond benchmark</th>
<th style="text-align:right">Go</th>
<th style="text-align:right">So</th>
<th>Winner</th>
</tr>
</thead>
<tbody>
<tr>
<td>1 waiter</td>
<td style="text-align:right">150ns</td>
<td style="text-align:right">1.5µs</td>
<td>Go - 10x</td>
</tr>
<tr>
<td>8 waiters</td>
<td style="text-align:right">2.0µs</td>
<td style="text-align:right">14µs</td>
<td>Go - 7.0x</td>
</tr>
<tr>
<td>32 waiters</td>
<td style="text-align:right">9.0µs</td>
<td style="text-align:right">60µs</td>
<td>Go - 6.7x</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Each number is the cost of one rendezvous round: a single broadcast that wakes every waiter and hands control back, with N waiters plus one broadcaster.</p>
</blockquote>
<p>Pthread-based condition variable is consistently 7-10 times slower. There's no trick to close this gap — it's just the cost of waking up a real OS thread instead of a goroutine.</p>
<p>Channels have the same issue because they're built using mutexes and condition variables:</p>
<table>
<thead>
<tr>
<th>Chan benchmark</th>
<th style="text-align:right">Go</th>
<th style="text-align:right">So</th>
<th>Winner</th>
</tr>
</thead>
<tbody>
<tr>
<td>Uncontended, 1 thread</td>
<td style="text-align:right">24ns</td>
<td style="text-align:right">21ns</td>
<td>So - 1.1x</td>
</tr>
<tr>
<td>Unbuffered, 2 threads</td>
<td style="text-align:right">130ns</td>
<td style="text-align:right">3.0µs</td>
<td>Go - 23x</td>
</tr>
<tr>
<td>Buffered (10), 2 threads</td>
<td style="text-align:right">44ns</td>
<td style="text-align:right">400ns</td>
<td>Go - 9.1x</td>
</tr>
<tr>
<td>Buffered (100), 2 threads</td>
<td style="text-align:right">33ns</td>
<td style="text-align:right">70ns</td>
<td>Go - 2.1x</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Each number is the cost of moving one value through the channel (send plus its matching receive). The number in parentheses is the buffer capacity.</p>
</blockquote>
<p>The uncontended case fills and drains a buffer from a single thread, so nothing ever blocks — it's just a lock plus a copy, which gives So a slight advantage. But the moment a producer and consumer actually start handing off work, So has to wake up a thread for every transfer that gets parked. It's worst for the unbuffered channel, where every value is a rendezvous with two wakeups: 23x slower. A larger buffer helps a lot — with room for 100 items, most sends go through without waking anyone, and the gap narrows to about 2x.</p>
<p>The consequence is that the larger your tasks are, the better pthread-based concurrency works. If you use a channel for fine-grained, value-at-a-time streaming between threads, performance will suffer. But if you use a channel to pass whole work items to a pool, where each item takes tens of microseconds to process, the wakeup cost becomes negligible. The pool benchmarks on realistic workloads confirms this:</p>
<table>
<thead>
<tr>
<th>Pool benchmark</th>
<th style="text-align:right">Go</th>
<th style="text-align:right">So</th>
<th>Winner</th>
</tr>
</thead>
<tbody>
<tr>
<td>1000 CPU tasks (~40µs each)</td>
<td style="text-align:right">7ms</td>
<td style="text-align:right">8ms</td>
<td>Go - 1.1x</td>
</tr>
<tr>
<td>64 IO tasks (1ms block each)</td>
<td style="text-align:right">9ms</td>
<td style="text-align:right">10ms</td>
<td>Go - 1.1x</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Each number is the wall-clock time for 8 workers to process the whole batch.</p>
</blockquote>
<p>Here, So is within 1.1x of Go. The per-task dispatch cost is still present, but it's spread out over real work, and the performance penalty is pretty small.</p>
<div class="boxed">
<p><strong>Benchmarking</strong></p>
<p>All benchmarks were run on an Apple M1 CPU running macOS. The C code was compiled with Clang 16 using these CFLAGS and mimalloc as the system allocator:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">-Ofast -march=native -flto -funroll-loops -DNDEBUG
</span></span></code></pre></div><p>The results shown are the medians from several benchmark runs. Each benchmark ran many iterations, following the same logic as Go's own benchmarking.</p>
<p>The Go benchmarks used Go 1.26 and <code>go test -bench=.</code>.</p>
<p>Source code for both So's and Go's benchmarks:
<a href="https://github.com/solod-dev/solod/tree/main/so/conc/bench">conc</a> •
<a href="https://github.com/solod-dev/solod/tree/main/so/sync/bench">sync</a></p>
</div>
<p>Here's a summary of the strengths and weaknesses of the pthread-based approach:</p>
<ul>
<li>➕ Coarse-grained pooled workloads are within about 10% of Go's performance.</li>
<li>➕ Uncontended locks and spin-friendly critical sections perform quite well.</li>
<li>➕ Atomic operations are as fast as in Go.</li>
<li>➕ The implementation is 100x simpler.</li>
<li>➖ Anything that needs to park and wake an OS thread is <em>much</em> slower than Go's userspace scheduler.</li>
<li>➖ The pool can't handle thousands of blocked waiters like goroutines can.</li>
</ul>
<p>If you're looking for &quot;thousands of cheap goroutines&quot;, the pthread-based approach will let you down. But if you're fine with &quot;a few worker threads handling lots of tasks&quot;, it holds up well.</p>
<h2 id="design-decisions">Design decisions</h2>
<p>Three decisions influenced the way I implemented concurrency in Solod.</p>
<p><strong>Pthreads, not fibers</strong>. I know there are coroutine/fiber libraries for C that avoid the kernel wakeup cost — single-threaded ones like <a href="https://github.com/tidwall/neco">neco</a>, and multi-threaded ones like <a href="https://github.com/iqiyi/libfiber">libfiber</a>. A userspace scheduler is exactly what would help to match Go in the benchmarks above.</p>
<p>I decided not to use one. I wanted something dead simple — an approach I could explain in a paragraph, using tools every C programmer already knows. The trade-off is that you lose some performance with fine-grained blocking, but in many real-world situations, pthreads work fine if you use a worker pool. For me, keeping things simple is more important than saving a few microseconds during task hand-offs. For now, at least.</p>
<p><strong>Standard library, not language</strong>. Go bakes goroutines, channels, and select right into the language. I decided to keep everything in the stdlib for two reasons.</p>
<p>➀ It follows So's &quot;no hidden allocations&quot; rule. In Go, <code>go f()</code> quietly allocates a goroutine stack, and <code>make(chan T, n)</code> allocates a buffer. In So, all allocations are explicit: you pass an allocator to <code>NewChan</code> and <code>NewPool</code>, and you always know exactly where the memory comes from — whether it's the system allocator, an arena, or something else.</p>
<p>➁ A library is more flexible. Since a pool is a regular value, you can have as many as you need, each sized for its specific purpose. In a multi-stage pipeline where each stage needs a different capacity, you can start one pool per stage, each with its own <code>NumThreads</code> and <code>QueueSize</code>, instead of being given a single global scheduler. The language stays simple, and the flexibility is in code you can easily read.</p>
<p><strong>Timeouts, not select</strong>. Go's <code>select</code> waits on several channel operations at once and proceeds with whichever is ready first. Implementing it would require a lot of work — a thread has to register interest on multiple channels, block once, and then wake up when any of them is ready — so I left it out. Instead, <code>Chan</code> offers <code>SendTimeout</code> and <code>RecvTimeout</code>, which cover two common uses of <code>select</code> with a single channel:</p>
<ul>
<li>&quot;Do this, but give up after a while&quot; (Go's <code>case &lt;-time.After(...)</code> idiom).</li>
<li>&quot;Do this only if it won't block&quot; (Go's non-blocking <code>default</code> branch).</li>
</ul>
<p>What's missing is the ability to block on multiple channels at once and continue with whichever one is ready first, as well as the option to mix sends and receives in the same selection.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>How close can you get to Go's concurrency using only pthreads? Close enough to be useful, but not enough to really match Go. You can wrap real OS threads with familiar APIs — mutexes, condition variables, pools, channels — and the code will look and act a lot like Go, at least until a thread needs to block. But there's no scheduler underneath, so when a thread blocks, it's an actual thread waiting in the kernel, not a goroutine that's paused for free. That's the main limitation of this approach.</p>
<p>What you get in return is brutal simplicity. Every primitive is a thin wrapper with no runtime hiding behind it, so the performance is exactly what the OS gives you: fast atomics, fast uncontended locks, and pooled throughput within ~10% of Go on coarse-grained work. But as soon as you switch to fine-grained, one-value-at-a-time hand-offs, the cost of kernel wakeups becomes the main factor, and you'll notice the slowdown.</p>
<p>If you think the pthread approach might work for you, I invite you to try <a href="https://github.com/solod-dev/solod">Solod</a>. It includes the <code>sync</code> and <code>conc</code> packages, along with many others ported from Go's standard library.</p>
]]></content:encoded></item><item><title>Solod 0.2: Networking, new targets, friendlier interop</title><link>https://antonz.org/solod-0.2/</link><pubDate>Fri, 26 Jun 2026 12:30:00 +0000</pubDate><guid>https://antonz.org/solod-0.2/</guid><description>A strict subset of Go that translates to regular C.</description><content:encoded><![CDATA[<p>Solod (<strong>So</strong>) is a subset of Go that translates to regular C — with zero runtime, manual memory management, and source-level interop. It's designed for two main audiences:</p>
<ul>
<li>Go developers who want low-level control without having to learn another language.</li>
<li>C developers who like Go's style.</li>
</ul>
<p>The <a href="/solod-0.1">previous version</a> (v0.1) focused on porting core Go stdlib packages and providing convenient C interop. At the end of that post, I said the next release would focus on networking, concurrency, or both. Now, networking is here — the v0.2 release I'm sharing today includes support for TCP, UDP, and Unix domain sockets. Concurrency is still planned for the future, so for now, servers handle one connection at a time.</p>
<p>This release also lets you compile So to more targets, like 32-bit platforms, WebAssembly, and bare metal. And C interop even smoother!</p>
<p><a href="#networking">Networking</a> •
<a href="#tcp-server">TCP server</a> •
<a href="#tcp-client">TCP client</a> •
<a href="#deadlines">Deadlines</a> •
<a href="#ip-addresses">IP addresses</a> •
<a href="#new-targets">Targets</a> •
<a href="#friendlier-interop">Interop</a> •
<a href="#more-stdlib">Stdlib</a> •
<a href="#wrapping-up">Wrapping up</a></p>
<h2 id="networking">Networking</h2>
<p>The main feature in v0.2 is the <code>net</code> package. It's a simplified version of Go's <code>net</code> package which supports the three most commonly used transports:</p>
<ul>
<li><strong>TCP</strong> (networks <code>tcp</code>, <code>tcp4</code>, <code>tcp6</code>) via <code>ResolveTCPAddr</code>, <code>DialTCP</code>, and <code>ListenTCP</code>, with the <code>TCPConn</code> and <code>TCPListener</code> types.</li>
<li><strong>UDP</strong> (networks <code>udp</code>, <code>udp4</code>, <code>udp6</code>) via <code>ResolveUDPAddr</code>, <code>DialUDP</code> (a connected socket), and <code>ListenUDP</code> (an unconnected socket with <code>ReadFrom</code>/<code>WriteTo</code>).</li>
<li><strong>Unix domain sockets</strong> (<code>unix</code> for streams, <code>unixgram</code> for datagrams) via <code>ResolveUnixAddr</code>, <code>DialUnix</code>, <code>ListenUnix</code>, and <code>ListenUnixgram</code>.</li>
</ul>
<p>The API mirrors Go closely, so most of it will feel familiar. The big difference is that So has no goroutines, so there's no concurrent server support — you accept and serve connections sequentially. More on that in a moment.</p>
<h2 id="tcp-server">TCP server</h2>
<p>Let's build a classic: an echo server that accepts a connection, reads a message, and sends it back.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="s">&#34;solod.dev/so/net&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Resolve the local address to listen on.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">laddr</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">ResolveTCPAddr</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="s">&#34;127.0.0.1:8080&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Start listening on the local address.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">ln</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">ListenTCP</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">laddr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nx">ln</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="s">&#34;listening on&#34;</span><span class="p">,</span> <span class="s">&#34;127.0.0.1:8080&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Accept connections and serve them in a loop.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">conn</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">ln</span><span class="p">.</span><span class="nf">Accept</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nf">serve</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">conn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// serve reads one message from the connection, echoes it back,
</span></span></span><span class="line"><span class="cl"><span class="c1">// and closes the connection.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">serve</span><span class="p">(</span><span class="nx">conn</span> <span class="o">*</span><span class="nx">net</span><span class="p">.</span><span class="nx">TCPConn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nx">conn</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">buf</span> <span class="p">[</span><span class="mi">256</span><span class="p">]</span><span class="kt">byte</span>
</span></span><span class="line"><span class="cl">    <span class="nx">n</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">conn</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">buf</span><span class="p">[:])</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">buf</span><span class="p">[:</span><span class="nx">n</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">listening on 127.0.0.1:8080
</span></span></code></pre></div><p>If you've written a TCP server in Go, this should look familiar — <code>ListenTCP</code>, an <code>Accept</code> loop, and <code>Read</code>/<code>Write</code> on the connection. The only thing missing is a <code>go serve(conn)</code>: without goroutines, each connection is handled to completion before moving on to the next <code>Accept</code>.</p>
<h2 id="tcp-client">TCP client</h2>
<p>The client starts the connection using <code>DialTCP</code>, then uses <code>Write</code> to send a request and <code>Read</code> to get the reply:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="s">&#34;solod.dev/so/net&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Resolve the server address.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">raddr</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">ResolveTCPAddr</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="s">&#34;127.0.0.1:8080&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// A nil laddr lets the system choose the local address.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">conn</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">DialTCP</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="kc">nil</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">raddr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nx">conn</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Send a request and read the reply.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">conn</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">&#34;hello&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">buf</span> <span class="p">[</span><span class="mi">256</span><span class="p">]</span><span class="kt">byte</span>
</span></span><span class="line"><span class="cl">    <span class="nx">n</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">conn</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">buf</span><span class="p">[:])</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">buf</span><span class="p">[:</span><span class="nx">n</span><span class="p">]))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">hello
</span></span></code></pre></div><p>UDP and Unix domain sockets work in a similar way. For UDP, an unconnected <code>ListenUDP</code> socket uses <code>ReadFrom</code> to get data and the sender's address, and <code>WriteTo</code> to send a reply. For Unix sockets, there are <code>ListenUnix</code> (stream) and <code>ListenUnixgram</code> (datagram).</p>
<h2 id="deadlines">Deadlines</h2>
<p>By default, <code>Accept</code>, <code>Read</code>, and <code>Write</code> are blocking. In Go, you'd typically use goroutines and contexts to prevent getting stuck forever. Since that's not available in So (yet), every connection and listener supports deadlines instead:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Give the client 5 seconds to send something.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">conn</span><span class="p">.</span><span class="nf">SetReadDeadline</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">().</span><span class="nf">Add</span><span class="p">(</span><span class="mi">5</span> <span class="o">*</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">n</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">conn</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">buf</span><span class="p">[:])</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">==</span> <span class="nx">net</span><span class="p">.</span><span class="nx">ErrTimeout</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// The client went quiet; drop the connection.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>SetDeadline</code>, <code>SetReadDeadline</code>, and <code>SetWriteDeadline</code> are available on <code>TCPConn</code>, <code>UDPConn</code>, <code>UnixConn</code>, and listener types. When the deadline passes, any pending call fails with <code>net.ErrTimeout</code>. If you don't set a deadline, a blocked call will wait forever. This isn't concurrency, but it's enough to keep a single-threaded server responsive.</p>
<h2 id="ip-addresses">IP addresses</h2>
<p>Along with <code>net</code>, v0.2 ports Go's <code>net/netip</code> package, which provides small, allocation-free value types for IP addresses. <code>Addr</code> represents an IP address, <code>AddrPort</code> combines an IP address with a port, and <code>Prefix</code> is an IP with a prefix length (a CIDR block):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">addr</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">netip</span><span class="p">.</span><span class="nf">ParseAddr</span><span class="p">(</span><span class="s">&#34;192.168.1.10&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">println</span><span class="p">(</span><span class="nx">addr</span><span class="p">.</span><span class="nf">Is4</span><span class="p">())</span>            <span class="c1">// true
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nx">ap</span> <span class="o">:=</span> <span class="nx">netip</span><span class="p">.</span><span class="nf">AddrPortFrom</span><span class="p">(</span><span class="nx">addr</span><span class="p">,</span> <span class="mi">8080</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">println</span><span class="p">(</span><span class="nx">ap</span><span class="p">.</span><span class="nf">Port</span><span class="p">())</span>             <span class="c1">// 8080
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nx">prefix</span> <span class="o">:=</span> <span class="nx">netip</span><span class="p">.</span><span class="nf">MustParsePrefix</span><span class="p">(</span><span class="s">&#34;192.168.1.0/24&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">println</span><span class="p">(</span><span class="nx">prefix</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nx">addr</span><span class="p">))</span> <span class="c1">// true
</span></span></span></code></pre></div><p>These are simple value types that don't use any heap allocation, which fits well with So's explicit-memory approach. The <code>net</code> package also provides <code>SplitHostPort</code> and <code>JoinHostPort</code> functions to help you work with <code>host:port</code> strings.</p>
<h2 id="new-targets">New targets</h2>
<p>Solod compiles to plain C, which (in theory) means it can target anything a C compiler can. Because of this, v0.2 adds new targets:</p>
<ul>
<li><strong>32-bit platforms</strong>. The compiler and stdlib now work correctly on 32-bit platforms, where <code>int</code> and pointers are narrower.</li>
<li><strong>WebAssembly (WASI)</strong>. You can compile a So program to <code>wasm32-wasi</code> and run it under any WASI runtime.</li>
<li><strong>Freestanding mode</strong>. So programs can run on bare-metal systems without any C standard library. No libc means no malloc, but you can use <code>mem.Arena</code> instead.</li>
</ul>
<p>Here's the complete toolchain you need to build a freestanding <code>wasm32</code> binary using <code>zig cc</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">CC</span><span class="o">=</span><span class="s2">&#34;zig cc&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">CFLAGS</span><span class="o">=</span><span class="s2">&#34;-Oz --target=wasm32-freestanding -nostdlib -Wl,--no-entry -Wl,--export=main&#34;</span>
</span></span><span class="line"><span class="cl">so build -o main.wasm .
</span></span></code></pre></div><p>A large part of the standard library (<code>bytes</code>, <code>strings</code>, <code>strconv</code>, <code>slices</code>, <code>maps</code>, <code>math</code>, <code>encoding/binary</code>, and more) works just fine in freestanding mode. For more details, check out the <a href="https://github.com/solod-dev/solod/blob/main/doc/freestanding.md">freestanding guide</a>.</p>
<h2 id="friendlier-interop">Friendlier interop</h2>
<p>A bunch of smaller changes make Solod nicer to write.</p>
<p><strong>Three new directives</strong> for low-level work, all documented in the <a href="https://github.com/solod-dev/solod/blob/main/doc/interop.md">interop guide</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">//so:volatile
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="nx">counter</span> <span class="kt">int</span>       <span class="c1">// emits a C volatile
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">//so:thread_local
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="nx">perThread</span> <span class="kt">int</span>     <span class="c1">// emits C11 _Thread_local
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">//so:attr packed
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">header</span> <span class="kd">struct</span> <span class="p">{</span>  <span class="c1">// emits __attribute__((packed))
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">version</span> <span class="kt">byte</span>
</span></span><span class="line"><span class="cl">    <span class="nx">length</span>  <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>so:attr</code> works with variables, constants, types, and functions. You can use it on multiple lines, and the attributes will stack. For example, <code>//so:attr aligned(16)</code> will combine with <code>//so:attr packed</code>.</p>
<p><strong>Type aliases</strong>. So now supports Go-style type aliases:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Byte</span> <span class="p">=</span> <span class="kt">uint8</span>
</span></span></code></pre></div><p><strong>Numeric C types</strong>. The <code>so/c</code> package now includes named types for C's numeric types — <code>Int</code>, <code>UInt</code>, <code>Long</code>, <code>Short</code>, <code>UChar</code>, <code>LongLong</code>, and others. When you declare an extern function, you can use the actual C types in its signature instead of trying to guess the correct fixed-width Go type for your platform.</p>
<p><strong>Third-party packages</strong>. You can now add external So packages using <code>go get</code> or by vendoring, and you can organize your own code into multiple modules. So doesn't have a real package ecosystem yet, but it's a good start.</p>
<p><strong>Better diagnostics</strong>. By default, panic messages report the C file and line. Pass <code>--track-source</code> to report the original So source location instead:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">so run --track-source .
</span></span></code></pre></div><p>There's also an optional <code>--check-nil</code> flag that adds nil-pointer checks when accessing struct fields and calling interface methods. This way, if there's a bad dereference, the program will panic cleanly instead of causing a segmentation fault. Both options are off by default to keep the generated code more readable.</p>
<h2 id="more-stdlib">More stdlib</h2>
<p>Beyond <code>net</code> and <code>net/netip</code>, v0.2 adds a few more packages:</p>
<ul>
<li><code>encoding/hex</code> — hex encoding and decoding, including <code>Dump</code> for hexdump-style output.</li>
<li><code>uuid</code> — generating and parsing UUIDs (v4 and v7), with random components from a cryptographically secure source.</li>
</ul>
<p>And a small but handy update to memory management: <code>mem.Arena.Free</code> now reclaims the last allocation if you give it the matching pointer. It's a minor optimization, but it means a quick alloc/free pair on an arena no longer wastes space.</p>
<p><a href="https://github.com/solod-dev/solod/blob/main/doc/stdlib.md">Stdlib documentation</a></p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>With v0.2, Solod has evolved from just &quot;command-line tools and C glue&quot; into something you can actually use on a network — like a TCP or UDP server, a small protocol client, or a Unix-socket daemon. The new targets (32-bit, WASM, freestanding) mean the same code can now run in more places, even down to bare metal.</p>
<p>The big thing that's still missing is concurrency. A server that handles requests one at a time works for some tasks, but a real network service needs to manage many connections at once. That's the obvious goal for the <a href="/solod-0.3">next release</a> — adding some kind of concurrency, along with the stdlib packages that support it.</p>
<p>If you're interested, take a look at So's <a href="https://github.com/solod-dev/solod#readme">readme</a> — it has everything you need to get started. Or <a href="https://codapi.org/so">try So online</a> without installing anything.</p>
]]></content:encoded></item><item><title>Solod 0.1: Go ergonomics, practical stdlib, native C interop</title><link>https://antonz.org/solod-0.1/</link><pubDate>Wed, 06 May 2026 11:00:00 +0000</pubDate><guid>https://antonz.org/solod-0.1/</guid><description>A strict subset of Go that translates to regular C.</description><content:encoded><![CDATA[<p>Solod (<strong>So</strong>) is a subset of Go that translates to regular C — with zero runtime, manual memory management, and source-level interop. It's designed for two main audiences:</p>
<ul>
<li>Go developers who want low-level control without having to learn another language.</li>
<li>C developers who like Go's style.</li>
</ul>
<p>The <a href="/solod">initial version</a> (let's call it v0) was focused on picking a subset of Go and translating it to C. The next logical step was to port Go's standard library and make it easier to interop with C. That's what the v0.1 release I'm presenting today is all about.</p>
<p><a href="#standard-library">Standard library</a> •
<a href="#sqlite-bindings">SQLite bindings</a> •
<a href="#persistent-map">Persistent map</a> •
<a href="#store-and-retrieve">Store and retrieve</a> •
<a href="#command-line-interface">Command-line interface</a> •
<a href="#performance">Performance</a> •
<a href="#wrapping-up">Wrapping up</a></p>
<h2 id="standard-library">Standard library</h2>
<p>Solod 0.1 ships with the following stdlib packages ported from Go:</p>
<ul>
<li><code>io</code>, <code>bufio</code>, and <code>fmt</code> — Abstractions and types for general-purpose I/O.</li>
<li><code>bytes</code>, <code>strings</code>, <code>strconv</code>, and <code>unicode/utf8</code> — Common byte and text operations.</li>
<li><code>slices</code> and <code>maps</code> — Generic heap-allocated data structures.</li>
<li><code>crypto/rand</code> and <code>math/rand</code> — Generating random data.</li>
<li><code>flag</code>, <code>os</code>, and <code>path</code> — Working with the command line and files.</li>
<li><code>log/slog</code> — Structured logging.</li>
<li><code>time</code> — Measuring and displaying time.</li>
</ul>
<p>And a couple of its own packages:</p>
<ul>
<li><code>mem</code> — Memory allocation with a pluggable allocator interface.</li>
<li><code>c</code> — Low-level C interop helpers.</li>
</ul>
<p><a href="https://github.com/solod-dev/solod/blob/main/doc/stdlib.md">Stdlib documentation</a></p>
<p>In the following sections, I'll demonstrate some of the features using a simple example: a persistent key-value store backed by SQLite.</p>
<h2 id="sqlite-bindings">SQLite bindings</h2>
<p>Since So doesn't provide <code>database/sql</code> yet, we'll call SQLite directly through its C API. To do this, let's import the necessary headers with the <code>so:include</code> directive and generate extern declarations using the <a href="https://github.com/solod-dev/sobind">sobind</a> tool:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="s">&#34;solod.dev/so/c&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">//so:include &lt;sqlite3.h&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// SQLite constants.
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1">//so:extern SQLITE_OK
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">const</span> <span class="nx">sqliteOK</span> <span class="p">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="c1">//so:extern SQLITE_ROW
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">const</span> <span class="nx">sqliteRow</span> <span class="p">=</span> <span class="mi">100</span>
</span></span><span class="line"><span class="cl"><span class="c1">//so:extern SQLITE_DONE
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">const</span> <span class="nx">sqliteDone</span> <span class="p">=</span> <span class="mi">101</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// SQLite types.
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1">//so:extern
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">sqlite3</span> <span class="kd">struct</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="c1">//so:extern
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">sqlite3_stmt</span> <span class="kd">struct</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="c1">//so:extern
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">sqlite3_value</span> <span class="kd">struct</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="c1">//so:extern
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">sqlite3_callback</span> <span class="kd">func</span><span class="p">(</span><span class="nx">any</span><span class="p">,</span> <span class="kt">int32</span><span class="p">,</span> <span class="o">**</span><span class="nx">c</span><span class="p">.</span><span class="nx">Char</span><span class="p">,</span> <span class="o">**</span><span class="nx">c</span><span class="p">.</span><span class="nx">Char</span><span class="p">)</span> <span class="kt">int32</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// SQLite functions.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">sqlite3_open</span><span class="p">(</span><span class="nx">filename</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">ppDb</span> <span class="o">**</span><span class="nx">sqlite3</span><span class="p">)</span> <span class="kt">int32</span>
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">sqlite3_prepare_v2</span><span class="p">(</span><span class="nx">db</span> <span class="o">*</span><span class="nx">sqlite3</span><span class="p">,</span> <span class="nx">zSql</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">nByte</span> <span class="kt">int32</span><span class="p">,</span> <span class="nx">ppStmt</span> <span class="o">**</span><span class="nx">sqlite3_stmt</span><span class="p">,</span> <span class="nx">pzTail</span> <span class="o">**</span><span class="nx">c</span><span class="p">.</span><span class="nx">ConstChar</span><span class="p">)</span> <span class="kt">int32</span>
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">sqlite3_step</span><span class="p">(</span><span class="nx">arg0</span> <span class="o">*</span><span class="nx">sqlite3_stmt</span><span class="p">)</span> <span class="kt">int32</span>
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">sqlite3_finalize</span><span class="p">(</span><span class="nx">pStmt</span> <span class="o">*</span><span class="nx">sqlite3_stmt</span><span class="p">)</span> <span class="kt">int32</span>
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">sqlite3_close</span><span class="p">(</span><span class="nx">arg0</span> <span class="o">*</span><span class="nx">sqlite3</span><span class="p">)</span> <span class="kt">int32</span>
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">sqlite3_exec</span><span class="p">(</span><span class="nx">arg0</span> <span class="o">*</span><span class="nx">sqlite3</span><span class="p">,</span> <span class="nx">sql</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">callback</span> <span class="nx">sqlite3_callback</span><span class="p">,</span> <span class="nx">arg3</span> <span class="nx">any</span><span class="p">,</span> <span class="nx">errmsg</span> <span class="o">**</span><span class="nx">c</span><span class="p">.</span><span class="nx">Char</span><span class="p">)</span> <span class="kt">int32</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// more declarations...
</span></span></span></code></pre></div><p>The <code>so:extern</code> directive is required for constants (<code>sqliteOK</code>) and types (<code>sqlite3_stmt</code>). As for functions (<code>sqlite3_prepare_v2</code>), we can just declare them without a body — the transpiler will treat them as extern declarations even without <code>so:extern</code>.</p>
<h2 id="persistent-map">Persistent map</h2>
<p>With the SQLite API in place, let's implement a key-value type that wraps the database connection:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// SQLMap is a simple key-value store backed by an SQLite database.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">SQLMap</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">db</span> <span class="o">*</span><span class="nx">sqlite3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Add a constructor that connects to an SQLite database and creates a table to store the items:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">ErrCreate</span> <span class="p">=</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;sqlmap: create schema failed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kd">const</span> <span class="nx">sqlCreate</span> <span class="p">=</span> <span class="s">&#34;create table if not exists kv (key text primary key, val)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// NewSQLMap creates a new SQLMap using the provided connection string.
</span></span></span><span class="line"><span class="cl"><span class="c1">// It opens a connection to the SQLite database and creates the underlying
</span></span></span><span class="line"><span class="cl"><span class="c1">// key-value table if it does not already exist.
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1">// The caller is responsible for calling Close on the returned SQLMap
</span></span></span><span class="line"><span class="cl"><span class="c1">// when it is no longer needed.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">NewSQLMap</span><span class="p">(</span><span class="nx">connStr</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">SQLMap</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">db</span> <span class="o">*</span><span class="nx">sqlite3</span>
</span></span><span class="line"><span class="cl">    <span class="nx">rc</span> <span class="o">:=</span> <span class="nf">sqlite3_open</span><span class="p">(</span><span class="nx">connStr</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">db</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">rc</span> <span class="o">!=</span> <span class="nx">sqliteOK</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">SQLMap</span><span class="p">{},</span> <span class="nx">ErrCreate</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">rc</span> <span class="p">=</span> <span class="nf">sqlite3_exec</span><span class="p">(</span><span class="nx">db</span><span class="p">,</span> <span class="nx">sqlCreate</span><span class="p">,</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">rc</span> <span class="o">!=</span> <span class="nx">sqliteOK</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sqlite3_close</span><span class="p">(</span><span class="nx">db</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">SQLMap</span><span class="p">{},</span> <span class="nx">ErrCreate</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">SQLMap</span><span class="p">{</span><span class="nx">db</span><span class="p">},</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Close releases resources associated with the SQLMap.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">m</span> <span class="o">*</span><span class="nx">SQLMap</span><span class="p">)</span> <span class="nf">Close</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sqlite3_close</span><span class="p">(</span><span class="nx">m</span><span class="p">.</span><span class="nx">db</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>As you can see, this So code looks a lot like regular Go code. However, there are some key differences:</p>
<ul>
<li>When compiled, the code is first translated to plain C, then compiled into a native binary using GCC or Clang.</li>
<li>Unlike Go, there is no runtime (no automatic heap memory allocation, no garbage collection, no goroutine scheduler).</li>
<li>There is no overhead when calling C functions, unlike Go's Cgo.</li>
<li>The interop syntax is a bit cleaner. For example, Go's <code>string</code> (<code>sqlCreate</code> in the <code>sqlite3_exec</code> call) automatically decays to C's <code>const char*</code>.</li>
</ul>
<h2 id="store-and-retrieve">Store and retrieve</h2>
<p>First, let's implement the <code>Set</code> method:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ErrPrepare</span> <span class="p">=</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;sqlmap: prepare failed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ErrExec</span>    <span class="p">=</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;sqlmap: exec failed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">const</span> <span class="nx">sqlSet</span> <span class="p">=</span> <span class="s">&#34;insert or replace into kv (key, val) values (?, ?)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Set stores a string value for the specified key.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">m</span> <span class="o">*</span><span class="nx">SQLMap</span><span class="p">)</span> <span class="nf">Set</span><span class="p">(</span><span class="nx">key</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">val</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">stmt</span> <span class="o">*</span><span class="nx">sqlite3_stmt</span>
</span></span><span class="line"><span class="cl">    <span class="nx">rc</span> <span class="o">:=</span> <span class="nf">sqlite3_prepare_v2</span><span class="p">(</span><span class="nx">m</span><span class="p">.</span><span class="nx">db</span><span class="p">,</span> <span class="nx">sqlSet</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">stmt</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">rc</span> <span class="o">!=</span> <span class="nx">sqliteOK</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">ErrPrepare</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nf">sqlite3_finalize</span><span class="p">(</span><span class="nx">stmt</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">sqlite3_bind_text</span><span class="p">(</span><span class="nx">stmt</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nb">int32</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">key</span><span class="p">)),</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sqlite3_bind_text</span><span class="p">(</span><span class="nx">stmt</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">val</span><span class="p">,</span> <span class="nb">int32</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">val</span><span class="p">)),</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">rc</span> <span class="p">=</span> <span class="nf">sqlite3_step</span><span class="p">(</span><span class="nx">stmt</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">rc</span> <span class="o">!=</span> <span class="nx">sqliteDone</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">ErrExec</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>No surprises here, just a bunch of SQLite API calls.</p>
<p>The <code>Get</code> method is more interesting:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">ErrNotFound</span> <span class="p">=</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;sqlmap: not found&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kd">const</span> <span class="nx">sqlGet</span> <span class="p">=</span> <span class="s">&#34;select val from kv where key = ?&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Get returns the value associated with the specified key.
</span></span></span><span class="line"><span class="cl"><span class="c1">// The caller owns the returned string and must free it with mem.FreeString.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">m</span> <span class="o">*</span><span class="nx">SQLMap</span><span class="p">)</span> <span class="nf">Get</span><span class="p">(</span><span class="nx">a</span> <span class="nx">mem</span><span class="p">.</span><span class="nx">Allocator</span><span class="p">,</span> <span class="nx">key</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">string</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">stmt</span> <span class="o">*</span><span class="nx">sqlite3_stmt</span>
</span></span><span class="line"><span class="cl">    <span class="nx">rc</span> <span class="o">:=</span> <span class="nf">sqlite3_prepare_v2</span><span class="p">(</span><span class="nx">m</span><span class="p">.</span><span class="nx">db</span><span class="p">,</span> <span class="nx">sqlGet</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">stmt</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">rc</span> <span class="o">!=</span> <span class="nx">sqliteOK</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">&#34;&#34;</span><span class="p">,</span> <span class="nx">ErrPrepare</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nf">sqlite3_finalize</span><span class="p">(</span><span class="nx">stmt</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">sqlite3_bind_text</span><span class="p">(</span><span class="nx">stmt</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nb">int32</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">key</span><span class="p">)),</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">rc</span> <span class="p">=</span> <span class="nf">sqlite3_step</span><span class="p">(</span><span class="nx">stmt</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">rc</span> <span class="o">==</span> <span class="nx">sqliteDone</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">&#34;&#34;</span><span class="p">,</span> <span class="nx">ErrNotFound</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">rc</span> <span class="o">!=</span> <span class="nx">sqliteRow</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">&#34;&#34;</span><span class="p">,</span> <span class="nx">ErrExec</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">text</span> <span class="o">:=</span> <span class="nf">sqlite3_column_text</span><span class="p">(</span><span class="nx">stmt</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">tmp</span> <span class="o">:=</span> <span class="nx">c</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nx">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">result</span> <span class="o">:=</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">Clone</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">tmp</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">result</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The pointer returned by <code>sqlite3_column_text</code> is managed by SQLite. It becomes invalid after calling <code>sqlite3_finalize</code> (which <code>Get</code> does before returning). Because of this, we need to allocate a copy of the returned value, using <code>strings.Clone</code> in this case.</p>
<p>So's approach to memory allocation is similar to Zig's — all heap allocations must be done explicitly by providing a specific instance of the <code>mem.Allocator</code> interface.</p>
<p>The caller, of course, must free the allocated string:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">m</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">NewSQLMap</span><span class="p">(</span><span class="s">&#34;:memory:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">defer</span> <span class="nx">m</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">m</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;name&#34;</span><span class="p">,</span> <span class="s">&#34;Alice&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">name</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">m</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="s">&#34;name&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="s">&#34;name =&#34;</span><span class="p">,</span> <span class="nx">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">mem</span><span class="p">.</span><span class="nf">FreeString</span><span class="p">(</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="nx">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">name = Alice
</span></span></code></pre></div><p>Here, <code>mem.System</code> is a specific allocator that uses libc's <code>malloc</code> and <code>free</code>. Alternatively, we could use <code>mem.Arena</code> or any other implementation of the <code>mem.Allocator</code> interface:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">buf</span> <span class="p">[</span><span class="mi">1024</span><span class="p">]</span><span class="kt">byte</span> <span class="c1">// stack-allocated
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">arena</span> <span class="o">:=</span> <span class="nx">mem</span><span class="p">.</span><span class="nf">NewArena</span><span class="p">(</span><span class="nx">buf</span><span class="p">[:])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">name</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">m</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">arena</span><span class="p">,</span> <span class="s">&#34;name&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">mem</span><span class="p">.</span><span class="nf">FreeString</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">arena</span><span class="p">,</span> <span class="nx">name</span><span class="p">)</span> <span class="c1">// no-op for arena; can be omitted
</span></span></span></code></pre></div><h2 id="command-line-interface">Command-line interface</h2>
<p>With the <code>SQLMap</code> type in place, let's create a simple CLI using the <code>flag</code> package:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">opFlag</span>  <span class="kt">string</span>
</span></span><span class="line"><span class="cl">    <span class="nx">keyFlag</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl">    <span class="nx">valFlag</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">parseFlags</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">flag</span><span class="p">.</span><span class="nf">StringVar</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">opFlag</span><span class="p">,</span> <span class="s">&#34;op&#34;</span><span class="p">,</span> <span class="s">&#34;&#34;</span><span class="p">,</span> <span class="s">&#34;operation: get, set, or del&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">flag</span><span class="p">.</span><span class="nf">StringVar</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">keyFlag</span><span class="p">,</span> <span class="s">&#34;key&#34;</span><span class="p">,</span> <span class="s">&#34;&#34;</span><span class="p">,</span> <span class="s">&#34;key name&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">flag</span><span class="p">.</span><span class="nf">StringVar</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">valFlag</span><span class="p">,</span> <span class="s">&#34;val&#34;</span><span class="p">,</span> <span class="s">&#34;&#34;</span><span class="p">,</span> <span class="s">&#34;value (for set operation)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">flag</span><span class="p">.</span><span class="nf">Parse</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">parseFlags</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>Then add command routing:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">m</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">NewSQLMap</span><span class="p">(</span><span class="s">&#34;sqlmap.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">check</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">defer</span> <span class="nx">m</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">switch</span> <span class="nx">opFlag</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s">&#34;set&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nx">err</span> <span class="p">=</span> <span class="nx">m</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="nx">keyFlag</span><span class="p">,</span> <span class="nx">valFlag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">check</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s">&#34;get&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nx">val</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">m</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="nx">keyFlag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">check</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">println</span><span class="p">(</span><span class="nx">val</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">mem</span><span class="p">.</span><span class="nf">FreeString</span><span class="p">(</span><span class="nx">mem</span><span class="p">.</span><span class="nx">System</span><span class="p">,</span> <span class="nx">val</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s">&#34;del&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nx">err</span> <span class="p">=</span> <span class="nx">m</span><span class="p">.</span><span class="nf">Delete</span><span class="p">(</span><span class="nx">keyFlag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">check</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nx">flag</span><span class="p">.</span><span class="nf">Usage</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="nx">os</span><span class="p">.</span><span class="nf">Exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sqlmap -op=set -key=name -val=alice
</span></span><span class="line"><span class="cl">sqlmap -op=get -key=name
</span></span><span class="line"><span class="cl">alice
</span></span></code></pre></div><p>Again, no surprises here — the <code>flag</code> package works just as it does in Go.</p>
<h2 id="performance">Performance</h2>
<p>Solod isn't trying to outperform hand-tuned C. Still, performance matters: the code is benchmarked and optimized to run reasonably fast. Since So compiles to plain C and then to native code with full optimizations, the results are sometimes better than Go's.</p>
<p>Here are some highlights from the benchmarks:</p>
<ul>
<li>Buffered I/O is 3x faster than Go.</li>
<li>String and byte operations are up to 2.5x faster.</li>
<li>Maps are 1.5x faster for modifications.</li>
<li>Integer formatting is 2x faster.</li>
</ul>
<p>There're no GC pauses and no Cgo bridge cost when calling C libraries. The tradeoff is that you have to handle memory yourself, but as the SQLite example above shows, So's allocator interface makes that pretty manageable.</p>
<p><a href="https://github.com/solod-dev/solod/blob/main/bench/README.md">Solod vs. Go benchmarks</a></p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>Solod is still in its early days, but with the v0.1 release, it's ready for hobby projects. The already-ported parts of the Go standard library make it easy to write command-line tools (check out the <code>cat</code>, <code>head</code>, <code>sort</code>, and <code>wc</code> <a href="https://github.com/solod-dev/example">examples</a>). Plus, with native C interop, you can build just about anything else you need.</p>
<p>The <a href="/solod-0.2">next release</a> will likely focus on networking, concurrency, or both — along with more stdlib packages.</p>
<p>If you're interested, take a look at So's <a href="https://github.com/solod-dev/solod#readme">readme</a> — it has all the information you need to get started. Or <a href="https://codapi.org/so">try So online</a> without installing anything.</p>
]]></content:encoded></item><item><title>Porting Go's strings package to C</title><link>https://antonz.org/porting-go-strings/</link><pubDate>Fri, 03 Apr 2026 13:00:00 +0000</pubDate><guid>https://antonz.org/porting-go-strings/</guid><description>With allocators, benchmarks, and some optimizations.</description><content:encoded><![CDATA[<p>Creating a subset of Go that <a href="/solod/">translates to C</a> was never my end goal. I liked writing C code with Go, but without the standard library it felt pretty limited. So, the next logical step was to port Go's stdlib to C.</p>
<p>Of course, this isn't something I could do all at once. I started with the <a href="/porting-go-io/">io package</a>, which provides core abstractions like <code>Reader</code> and <code>Writer</code>, as well as general-purpose functions like <code>Copy</code>. But <code>io</code> isn't very interesting on its own, since it doesn't include specific reader or writer implementations. So my next choices were naturally <code>bytes</code> and <code>strings</code> — the workhorses of almost every Go program. This post is about how the porting process went.</p>
<p><a href="#bits-and-utf-8">Bits and UTF-8</a> •
<a href="#bytes">Bytes</a> •
<a href="#allocators">Allocators</a> •
<a href="#buffers-and-builders">Buffers and builders</a> •
<a href="#benchmarks">Benchmarks</a> •
<a href="#optimizing-search">Optimizing search</a> •
<a href="#optimizing-builder">Optimizing builder</a> •
<a href="#wrapping-up">Wrapping up</a></p>
<h2 id="bits-and-utf-8">Bits and UTF-8</h2>
<p>Before I could start porting <code>bytes</code>, I had to deal with its dependencies first:</p>
<ul>
<li><code>math/bits</code> implements bit counting and manipulation functions.</li>
<li><code>unicode/utf8</code> implements functions for UTF-8 encoded text.</li>
</ul>
<p>Both of these packages are made up of pure functions, so they were pretty easy to port. The only minor challenge was the difference in operator precedence between Go and C — specifically, bit shifts (<code>&lt;&lt;</code>, <code>&gt;&gt;</code>). In Go, bit shifts have higher precedence than addition and subtraction. In C, they have lower precedence:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Go: shift has HIGHER precedence than +
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="nx">x</span> <span class="kt">uint32</span> <span class="p">=</span> <span class="mi">1</span><span class="o">&lt;&lt;</span><span class="mi">2</span> <span class="o">+</span> <span class="mi">3</span>  <span class="c1">// (1 &lt;&lt; 2) + 3 == 7
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// C: shift has LOWER precedence than +
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">uint32_t</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">3</span><span class="p">;</span> <span class="c1">// 1 &lt;&lt; (2 + 3) == 32
</span></span></span></code></pre></div><p>The simplest solution was to just use parentheses everywhere shifts are involved:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Go: Mul64 returns the 128-bit product of x and y: (hi, lo) = x * y
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Mul64</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span> <span class="kt">uint64</span><span class="p">)</span> <span class="p">(</span><span class="nx">hi</span><span class="p">,</span> <span class="nx">lo</span> <span class="kt">uint64</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">const</span> <span class="nx">mask32</span> <span class="p">=</span> <span class="mi">1</span><span class="o">&lt;&lt;</span><span class="mi">32</span> <span class="o">-</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="nx">x0</span> <span class="o">:=</span> <span class="nx">x</span> <span class="o">&amp;</span> <span class="nx">mask32</span>
</span></span><span class="line"><span class="cl">    <span class="nx">x1</span> <span class="o">:=</span> <span class="nx">x</span> <span class="o">&gt;&gt;</span> <span class="mi">32</span>
</span></span><span class="line"><span class="cl">    <span class="nx">y0</span> <span class="o">:=</span> <span class="nx">y</span> <span class="o">&amp;</span> <span class="nx">mask32</span>
</span></span><span class="line"><span class="cl">    <span class="nx">y1</span> <span class="o">:=</span> <span class="nx">y</span> <span class="o">&gt;&gt;</span> <span class="mi">32</span>
</span></span><span class="line"><span class="cl">    <span class="nx">w0</span> <span class="o">:=</span> <span class="nx">x0</span> <span class="o">*</span> <span class="nx">y0</span>
</span></span><span class="line"><span class="cl">    <span class="nx">t</span> <span class="o">:=</span> <span class="nx">x1</span><span class="o">*</span><span class="nx">y0</span> <span class="o">+</span> <span class="nx">w0</span><span class="o">&gt;&gt;</span><span class="mi">32</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// C: Mul64 returns the 128-bit product of x and y: (hi, lo) = x * y
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">so_Result</span> <span class="nf">bits_Mul64</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">x</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="n">so_int</span> <span class="n">mask32</span> <span class="o">=</span> <span class="p">((</span><span class="n">so_int</span><span class="p">)</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">32</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">x0</span> <span class="o">=</span> <span class="p">(</span><span class="n">x</span> <span class="o">&amp;</span> <span class="n">mask32</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">x1</span> <span class="o">=</span> <span class="p">(</span><span class="n">x</span> <span class="o">&gt;&gt;</span> <span class="mi">32</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">y0</span> <span class="o">=</span> <span class="p">(</span><span class="n">y</span> <span class="o">&amp;</span> <span class="n">mask32</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">y1</span> <span class="o">=</span> <span class="p">(</span><span class="n">y</span> <span class="o">&gt;&gt;</span> <span class="mi">32</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">w0</span> <span class="o">=</span> <span class="n">x0</span> <span class="o">*</span> <span class="n">y0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">t</span> <span class="o">=</span> <span class="n">x1</span> <span class="o">*</span> <span class="n">y0</span> <span class="o">+</span> <span class="p">(</span><span class="n">w0</span> <span class="o">&gt;&gt;</span> <span class="mi">32</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>With <code>bits</code> and <code>utf8</code> done, I moved on to <code>bytes</code>.</p>
<h2 id="bytes">Bytes</h2>
<p>The <code>bytes</code> package provides functions for working with byte slices:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Count counts the number of non-overlapping instances of sep in s.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Count</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span> <span class="nx">sep</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Equal reports whether a and b are the
</span></span></span><span class="line"><span class="cl"><span class="c1">// same length and contain the same bytes.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Equal</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="kt">bool</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Index returns the index of the first instance
</span></span></span><span class="line"><span class="cl"><span class="c1">// of sep in s, or -1 if sep is not present in s.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Index</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span> <span class="nx">sep</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Repeat returns a new byte slice consisting of count copies of b.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Repeat</span><span class="p">(</span><span class="nx">b</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">,</span> <span class="nx">count</span> <span class="kt">int</span><span class="p">)</span> <span class="p">[]</span><span class="kt">byte</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// and others
</span></span></span></code></pre></div><p>Some of them were easy to port, like <code>Equal</code>. Here's how it looks in Go:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Equal reports whether a and b are the
</span></span></span><span class="line"><span class="cl"><span class="c1">// same length and contain the same bytes.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Equal</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Neither cmd/compile nor gccgo allocates for these string conversions.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="nb">string</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">==</span> <span class="nb">string</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And here's the C version:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// bytes_string reinterprets a byte slice as a string (zero-copy).
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#define so_bytes_string(bs) ({                  \
</span></span></span><span class="line"><span class="cl"><span class="cp">    so_Slice _bs = (bs);                        \
</span></span></span><span class="line"><span class="cl"><span class="cp">    (so_String){(const char*)_bs.ptr, _bs.len}; \
</span></span></span><span class="line"><span class="cl"><span class="cp">})
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// string_eq returns true if two strings are equal.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">static</span> <span class="kr">inline</span> <span class="kt">bool</span> <span class="nf">so_string_eq</span><span class="p">(</span><span class="n">so_String</span> <span class="n">s1</span><span class="p">,</span> <span class="n">so_String</span> <span class="n">s2</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">s1</span><span class="p">.</span><span class="n">len</span> <span class="o">==</span> <span class="n">s2</span><span class="p">.</span><span class="n">len</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">s1</span><span class="p">.</span><span class="n">len</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="nf">memcmp</span><span class="p">(</span><span class="n">s1</span><span class="p">.</span><span class="n">ptr</span><span class="p">,</span> <span class="n">s2</span><span class="p">.</span><span class="n">ptr</span><span class="p">,</span> <span class="n">s1</span><span class="p">.</span><span class="n">len</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Equal reports whether a and b are the
</span></span></span><span class="line"><span class="cl"><span class="c1">// same length and contain the same bytes.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">bool</span> <span class="nf">bytes_Equal</span><span class="p">(</span><span class="n">so_Slice</span> <span class="n">a</span><span class="p">,</span> <span class="n">so_Slice</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nf">so_string_eq</span><span class="p">(</span><span class="nf">so_bytes_string</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="nf">so_bytes_string</span><span class="p">(</span><span class="n">b</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Just like in Go, the <code>so_bytes_string</code> (<code>[]byte</code> → <code>string</code>) macro doesn't allocate memory; it just reinterprets the byte slice's underlying storage as a string. The <code>so_string_eq</code> function (which works like <code>==</code> in Go) is easy to implement using <code>memcmp</code> from the libc API.</p>
<p>Another example is the <code>IndexByte</code> function, which looks for a specific byte in a slice. Here's the pure-Go implementation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// IndexByte returns the index of the first instance
</span></span></span><span class="line"><span class="cl"><span class="c1">// of c in b, or -1 if c is not present in b.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">IndexByte</span><span class="p">(</span><span class="nx">b</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">,</span> <span class="nx">c</span> <span class="kt">byte</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">x</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">b</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nx">x</span> <span class="o">==</span> <span class="nx">c</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nx">i</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And here's the C version:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// IndexByte returns the index of the first instance
</span></span></span><span class="line"><span class="cl"><span class="c1">// of c in b, or -1 if c is not present in b.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">so_int</span> <span class="nf">bytes_IndexByte</span><span class="p">(</span><span class="n">so_Slice</span> <span class="n">b</span><span class="p">,</span> <span class="n">so_byte</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="n">so_int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="nf">so_len</span><span class="p">(</span><span class="n">b</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">so_byte</span> <span class="n">x</span> <span class="o">=</span> <span class="nf">so_at</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">x</span> <span class="o">==</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I used a regular C <code>for</code> loop to mimic Go's <code>for-range</code>:</p>
<ul>
<li>Loop over the slice indexes with <code>for</code> (<code>so_len</code> is a macro that returns <code>b.len</code>, similar to Go's <code>len</code> built-in).</li>
<li>Access the i-th byte with <code>so_at</code> (a bounds-checking macro that returns <code>*((so_byte*)b.ptr + i)</code>).</li>
</ul>
<p>But <code>Equal</code> and <code>IndexByte</code> don't allocate memory. What should I do with <code>Repeat</code>, since it clearly does? I had a decision to make.</p>
<h2 id="allocators">Allocators</h2>
<p>The Go runtime handles memory allocation and deallocation automatically. In C, I had a few options:</p>
<ul>
<li>Use a reliable garbage collector like Boehm GC to closely match Go's behavior.</li>
<li>Allocate memory with libc's <code>malloc</code> and have the caller free it later with <code>free</code>.</li>
<li>Introduce allocators.</li>
</ul>
<blockquote>
<p>An <em>allocator</em> is a tool that reserves memory (typically on the heap) so a program can store its data structures there. See <a href="/allocators/">Allocators from C to Zig</a> if you want to learn more about them.</p>
</blockquote>
<p>For me, the winner was clear. Modern systems programming languages like Zig and Odin clearly showed the value of allocators:</p>
<ul>
<li>It's obvious whether a function allocates memory or not: if it has an allocator as a parameter, it allocates.</li>
<li>It's easy to use different allocation methods: you can use <code>malloc</code> for one function, an arena for another, and a stack allocator for a third.</li>
<li>It helps with testing and debugging: you can use a tracking allocator to find memory leaks, or a failing allocator to test error handling.</li>
</ul>
<p>An <code>Allocator</code> is an interface with three methods: <code>Alloc</code>, <code>Realloc</code>, and <code>Free</code>. In C, it translates to a struct with function pointers:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Allocator defines the interface for memory allocators.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_Result</span> <span class="p">(</span><span class="o">*</span><span class="n">Alloc</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">align</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_Result</span> <span class="p">(</span><span class="o">*</span><span class="n">Realloc</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">so_int</span> <span class="n">oldSize</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">newSize</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">align</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">Free</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">align</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">mem_Allocator</span><span class="p">;</span>
</span></span></code></pre></div><blockquote>
<p>As I mentioned in the post about <a href="/porting-go-io/">porting the io package</a>, this interface representation isn't as efficient as using a static method table, but it's simpler. If you're interested in other options, check out the post on <a href="/interfaces-in-c/">interfaces</a>.</p>
</blockquote>
<p>By convention, if a function allocates memory, it takes an allocator as its first parameter. So Go's <code>Repeat</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Repeat returns a new byte slice consisting of count copies of b.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Repeat</span><span class="p">(</span><span class="nx">b</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">,</span> <span class="nx">count</span> <span class="kt">int</span><span class="p">)</span> <span class="p">[]</span><span class="kt">byte</span>
</span></span></code></pre></div><p>Translates to this C code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Repeat returns a new byte slice consisting of count copies of b.
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1">// If the allocator is nil, uses the system allocator.
</span></span></span><span class="line"><span class="cl"><span class="c1">// The returned slice is allocated; the caller owns it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">so_Slice</span> <span class="nf">bytes_Repeat</span><span class="p">(</span><span class="n">mem_Allocator</span> <span class="n">a</span><span class="p">,</span> <span class="n">so_Slice</span> <span class="n">b</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">count</span><span class="p">)</span>
</span></span></code></pre></div><p>If the caller doesn't care about using a specific allocator, they can just pass an empty allocator, and the implementation will use the system allocator — <code>calloc</code>, <code>realloc</code>, and <code>free</code> from libc.</p>
<p>Here's a simplified version of the system allocator (I removed safety checks to make it easier to read):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// SystemAllocator uses the system&#39;s malloc, realloc, and free functions.
</span></span></span><span class="line"><span class="cl"><span class="c1">// It zeros out new memory on allocation and reallocation.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{}</span> <span class="n">mem_SystemAllocator</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">so_Result</span> <span class="nf">mem_SystemAllocator_Alloc</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">align</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span> <span class="o">=</span> <span class="nf">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="p">(</span><span class="kt">size_t</span><span class="p">)(</span><span class="n">size</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">ptr</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="n">so_Result</span><span class="p">){.</span><span class="n">val</span><span class="p">.</span><span class="n">as_ptr</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="n">mem_ErrOutOfMemory</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">so_Result</span><span class="p">){</span> <span class="p">.</span><span class="n">val</span><span class="p">.</span><span class="n">as_ptr</span> <span class="o">=</span> <span class="n">ptr</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">so_Result</span> <span class="nf">mem_SystemAllocator_Realloc</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">oldSize</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">newSize</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">align</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">newPtr</span> <span class="o">=</span> <span class="nf">realloc</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="p">(</span><span class="kt">size_t</span><span class="p">)(</span><span class="n">newSize</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">newPtr</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="n">so_Result</span><span class="p">){.</span><span class="n">val</span><span class="p">.</span><span class="n">as_ptr</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="n">mem_ErrOutOfMemory</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">newSize</span> <span class="o">&gt;</span> <span class="n">oldSize</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Zero new memory beyond the old size.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nf">memset</span><span class="p">((</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">newPtr</span> <span class="o">+</span> <span class="n">oldSize</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="kt">size_t</span><span class="p">)(</span><span class="n">newSize</span> <span class="o">-</span> <span class="n">oldSize</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">so_Result</span><span class="p">){.</span><span class="n">val</span><span class="p">.</span><span class="n">as_ptr</span> <span class="o">=</span> <span class="n">newPtr</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">mem_SystemAllocator_Free</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">align</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">free</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The system allocator is stateless, so it's safe to have a global instance:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// System is an instance of a memory allocator that uses
</span></span></span><span class="line"><span class="cl"><span class="c1">// the system&#39;s malloc, realloc, and free functions.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">mem_Allocator</span> <span class="n">mem_System</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">self</span> <span class="o">=</span> <span class="o">&amp;</span><span class="p">(</span><span class="n">mem_SystemAllocator</span><span class="p">){},</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">Alloc</span> <span class="o">=</span> <span class="n">mem_SystemAllocator_Alloc</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">Free</span> <span class="o">=</span> <span class="n">mem_SystemAllocator_Free</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">Realloc</span> <span class="o">=</span> <span class="n">mem_SystemAllocator_Realloc</span><span class="p">};</span>
</span></span></code></pre></div><p>Here's an example of how to call <code>Repeat</code> with an allocator:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">so_Slice</span> <span class="n">src</span> <span class="o">=</span> <span class="nf">so_string_bytes</span><span class="p">(</span><span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;abc&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="n">so_Slice</span> <span class="n">got</span> <span class="o">=</span> <span class="nf">bytes_Repeat</span><span class="p">(</span><span class="n">mem_System</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">so_String</span> <span class="n">gotStr</span> <span class="o">=</span> <span class="nf">so_bytes_string</span><span class="p">(</span><span class="n">got</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nf">so_string_ne</span><span class="p">(</span><span class="n">gotStr</span><span class="p">,</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;abcabcabc&#34;</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_panic</span><span class="p">(</span><span class="s">&#34;want Repeat(abc) == abcabcabc&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nf">mem_FreeSlice</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">mem_System</span><span class="p">,</span> <span class="n">got</span><span class="p">);</span>
</span></span></code></pre></div><p>Way better than hidden allocations!</p>
<h2 id="buffers-and-builders">Buffers and builders</h2>
<p>Besides pure functions, <code>bytes</code> and <code>strings</code> also provide types like <code>bytes.Buffer</code>, <code>bytes.Reader</code>, and <code>strings.Builder</code>. I ported them using the same approach as with functions.</p>
<p>For types that allocate memory, like <code>Buffer</code>, the allocator becomes a struct field:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// A Buffer is a variable-sized buffer of bytes
</span></span></span><span class="line"><span class="cl"><span class="c1">// with Read and Write methods.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Allocator</span> <span class="n">a</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">off</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">bytes_Buffer</span><span class="p">;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Usage example.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">bytes_Buffer</span> <span class="n">buf</span> <span class="o">=</span> <span class="nf">bytes_NewBuffer</span><span class="p">(</span><span class="n">mem_System</span><span class="p">,</span> <span class="p">(</span><span class="n">so_Slice</span><span class="p">){</span><span class="mi">0</span><span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="nf">bytes_Buffer_WriteString</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">,</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;hello&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="nf">bytes_Buffer_WriteString</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">,</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34; world&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="n">so_String</span> <span class="n">str</span> <span class="o">=</span> <span class="nf">bytes_Buffer_String</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nf">so_string_ne</span><span class="p">(</span><span class="n">str</span><span class="p">,</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;hello world&#34;</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_panic</span><span class="p">(</span><span class="s">&#34;Buffer.WriteString failed&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nf">bytes_Buffer_Free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">);</span>
</span></span></code></pre></div><blockquote>
<p>The code is pretty wordy — most C developers would dislike using <code>bytes_Buffer_WriteString</code> instead of something shorter like <code>buf_writestr</code>. My solution to this problem is to automatically translate Go code to C (which is actually what I do when porting Go's stdlib). If you're interested, check out the post about this approach — <a href="/solod/">Solod: Go can be a better C</a>.</p>
</blockquote>
<p>Types that don't allocate, like <code>bytes.Reader</code>, need no special treatment — they translate directly to C structs without an allocator field.</p>
<p>The <code>strings</code> package is the twin of <code>bytes</code>, so porting it was uneventful. Here's <code>strings.Builder</code> usage example in Go and C side by side:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// go
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="nx">sb</span> <span class="nx">strings</span><span class="p">.</span><span class="nx">Builder</span>
</span></span><span class="line"><span class="cl"><span class="nx">sb</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="s">&#34;Hello&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">sb</span><span class="p">.</span><span class="nf">WriteByte</span><span class="p">(</span><span class="sc">&#39;,&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">sb</span><span class="p">.</span><span class="nf">WriteRune</span><span class="p">(</span><span class="sc">&#39; &#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">sb</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="s">&#34;world&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">s</span> <span class="o">:=</span> <span class="nx">sb</span><span class="p">.</span><span class="nf">String</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">s</span> <span class="o">!=</span> <span class="s">&#34;Hello, world&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">panic</span><span class="p">(</span><span class="s">&#34;want sb.String() == &#39;Hello, world&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">strings_Builder</span> <span class="n">sb</span> <span class="o">=</span> <span class="p">{.</span><span class="n">a</span> <span class="o">=</span> <span class="n">mem_System</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="nf">strings_Builder_WriteString</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sb</span><span class="p">,</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;Hello&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="nf">strings_Builder_WriteByte</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sb</span><span class="p">,</span> <span class="sc">&#39;,&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">strings_Builder_WriteRune</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sb</span><span class="p">,</span> <span class="n">U</span><span class="sc">&#39; &#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">strings_Builder_WriteString</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sb</span><span class="p">,</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;world&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="n">so_String</span> <span class="n">s</span> <span class="o">=</span> <span class="nf">strings_Builder_String</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sb</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nf">so_string_ne</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;Hello, world&#34;</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_panic</span><span class="p">(</span><span class="s">&#34;want sb.String() == &#39;Hello, world&#39;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nf">strings_Builder_Free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sb</span><span class="p">);</span>
</span></span></code></pre></div><p>Again, the C code is just a more verbose version of Go's implementation, plus explicit memory allocation.</p>
<h2 id="benchmarks">Benchmarks</h2>
<p>What's the point of writing C code if it's slow, right? I decided it was time to benchmark the ported C types and functions against their Go versions.</p>
<p>To do that, I ported the benchmarking part of Go's <code>testing</code> package. Surprisingly, the simplified version was only 300 lines long and included everything I needed:</p>
<ul>
<li>Figuring out how many iterations to run.</li>
<li>Running the benchmark function in a loop.</li>
<li>Recording metrics (ns/op, MB/s, B/op, allocs/op).</li>
<li>Reporting the results.</li>
</ul>
<p>Here's a sample benchmark for the <code>strings.Builder</code> type:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">so_String</span> <span class="n">someStr</span> <span class="o">=</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;some string sdljlk jsklj3lkjlk djlkjw&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">const</span> <span class="n">so_int</span> <span class="n">numWrite</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">volatile</span> <span class="n">so_String</span> <span class="n">sink</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">main_WriteString_AutoGrow</span><span class="p">(</span><span class="n">testing_B</span><span class="o">*</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Allocator</span> <span class="n">a</span> <span class="o">=</span> <span class="nf">testing_B_Allocator</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;</span> <span class="nf">testing_B_Loop</span><span class="p">(</span><span class="n">b</span><span class="p">);)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">strings_Builder</span> <span class="n">sb</span> <span class="o">=</span> <span class="nf">strings_NewBuilder</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="n">so_int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">numWrite</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">strings_Builder_WriteString</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sb</span><span class="p">,</span> <span class="n">someStr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">sink</span> <span class="o">=</span> <span class="nf">strings_Builder_String</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sb</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">strings_Builder_Free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sb</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// more benchmarks...
</span></span></span></code></pre></div><p>Reads almost like Go's benchmarks.</p>
<p>To monitor memory usage, I created <code>Tracker</code> — a memory allocator that wraps another allocator and keeps track of allocations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// A Stats records statistics about the memory allocator.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">Alloc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">TotalAlloc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">Mallocs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">Frees</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">mem_Stats</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// A Tracker wraps an Allocator and tracks all
</span></span></span><span class="line"><span class="cl"><span class="c1">// allocations and deallocations made through it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Allocator</span> <span class="n">Allocator</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Stats</span> <span class="n">Stats</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">mem_Tracker</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">so_Result</span> <span class="nf">mem_Tracker_Alloc</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">align</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Tracker</span><span class="o">*</span> <span class="n">t</span> <span class="o">=</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Result</span> <span class="n">res</span> <span class="o">=</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">Allocator</span><span class="p">.</span><span class="nf">Alloc</span><span class="p">(</span><span class="n">t</span><span class="o">-&gt;</span><span class="n">Allocator</span><span class="p">.</span><span class="n">self</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">align</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">t</span><span class="o">-&gt;</span><span class="n">Stats</span><span class="p">.</span><span class="n">Alloc</span> <span class="o">+=</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)(</span><span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">t</span><span class="o">-&gt;</span><span class="n">Stats</span><span class="p">.</span><span class="n">TotalAlloc</span> <span class="o">+=</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)(</span><span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">t</span><span class="o">-&gt;</span><span class="n">Stats</span><span class="p">.</span><span class="n">Mallocs</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">so_Result</span><span class="p">){.</span><span class="n">val</span><span class="p">.</span><span class="n">as_ptr</span> <span class="o">=</span> <span class="n">res</span><span class="p">.</span><span class="n">val</span><span class="p">.</span><span class="n">as_ptr</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">mem_Tracker_Free</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">size</span><span class="p">,</span> <span class="n">so_int</span> <span class="n">align</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mem_Tracker</span><span class="o">*</span> <span class="n">t</span> <span class="o">=</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">t</span><span class="o">-&gt;</span><span class="n">Allocator</span><span class="p">.</span><span class="nf">Free</span><span class="p">(</span><span class="n">t</span><span class="o">-&gt;</span><span class="n">Allocator</span><span class="p">.</span><span class="n">self</span><span class="p">,</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">align</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">t</span><span class="o">-&gt;</span><span class="n">Stats</span><span class="p">.</span><span class="n">Alloc</span> <span class="o">-=</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)(</span><span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">t</span><span class="o">-&gt;</span><span class="n">Stats</span><span class="p">.</span><span class="n">Frees</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The benchmark gets an allocator through the <code>testing_RunBenchmarks</code> function and wraps it in a <code>Tracker</code> to keep track of allocations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">benchs</span> <span class="o">=</span> <span class="p">{(</span><span class="n">testing_Benchmark</span><span class="p">[</span><span class="mi">4</span><span class="p">]){</span>
</span></span><span class="line"><span class="cl">        <span class="p">{.</span><span class="n">Name</span> <span class="o">=</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;WriteS_AutoGrow&#34;</span><span class="p">),</span> <span class="p">.</span><span class="n">F</span> <span class="o">=</span> <span class="n">main_WriteString_AutoGrow</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{.</span><span class="n">Name</span> <span class="o">=</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;WriteS_PreGrow&#34;</span><span class="p">),</span> <span class="p">.</span><span class="n">F</span> <span class="o">=</span> <span class="n">main_WriteString_PreGrow</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{.</span><span class="n">Name</span> <span class="o">=</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;WriteB_AutoGrow&#34;</span><span class="p">),</span> <span class="p">.</span><span class="n">F</span> <span class="o">=</span> <span class="n">main_Write_AutoGrow</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{.</span><span class="n">Name</span> <span class="o">=</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;WriteB_PreGrow&#34;</span><span class="p">),</span> <span class="p">.</span><span class="n">F</span> <span class="o">=</span> <span class="n">main_Write_PreGrow</span><span class="p">}},</span>
</span></span><span class="line"><span class="cl">        <span class="mi">4</span><span class="p">,</span> <span class="mi">4</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="nf">testing_RunBenchmarks</span><span class="p">(</span><span class="n">mem_System</span><span class="p">,</span> <span class="n">benchs</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>There's no auto-discovery, but the manual setup is quite straightforward.</p>
<h2 id="optimizing-search">Optimizing search</h2>
<p>With the benchmarking setup ready, I ran benchmarks on the <code>strings</code> package. Some functions did well — about 1.5-2x faster than their Go equivalents:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">go
</span></span><span class="line"><span class="cl">Benchmark_Clone-8      12143073      98.50 ns/op    1024 B/op    1 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_Fields-8       791077    1524 ns/op        288 B/op    1 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_Repeat-8      9197040     127.3 ns/op     1024 B/op    1 allocs/op
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">c
</span></span><span class="line"><span class="cl">Benchmark_Clone        27935466      41.84 ns/op    1024 B/op    1 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_Fields        1319384     907.7 ns/op      272 B/op    1 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_Repeat       18445929      64.11 ns/op    1024 B/op    1 allocs/op
</span></span></code></pre></div><p>But <code>Index</code> (searching for a substring in a string) was a total disaster — it was nearly 20 times slower than in Go:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">go
</span></span><span class="line"><span class="cl">Benchmark_Index-8      47874408      25.14 ns/op       0 B/op    0 allocs/op
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">c
</span></span><span class="line"><span class="cl">Benchmark_Index          483787     483.1 ns/op        0 B/op    0 allocs/op
</span></span></code></pre></div><p>The problem was caused by the <code>IndexByte</code> function we looked at earlier:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// IndexByte returns the index of the first instance
</span></span></span><span class="line"><span class="cl"><span class="c1">// of c in b, or -1 if c is not present in b.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">IndexByte</span><span class="p">(</span><span class="nx">b</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">,</span> <span class="nx">c</span> <span class="kt">byte</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">x</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">b</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nx">x</span> <span class="o">==</span> <span class="nx">c</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nx">i</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This &quot;pure&quot; Go implementation is just a fallback. On most platforms, Go uses a specialized version of <code>IndexByte</code> written in assembly.</p>
<p>For the C version, the easiest solution was to use <code>memchr</code>, which is also optimized for most platforms:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="kr">inline</span> <span class="n">so_int</span> <span class="nf">bytealg_IndexByte</span><span class="p">(</span><span class="n">so_Slice</span> <span class="n">b</span><span class="p">,</span> <span class="n">so_byte</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">at</span> <span class="o">=</span> <span class="nf">memchr</span><span class="p">(</span><span class="n">b</span><span class="p">.</span><span class="n">ptr</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">c</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">at</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">so_int</span><span class="p">)((</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">at</span> <span class="o">-</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">b</span><span class="p">.</span><span class="n">ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>With this fix, the benchmark results changed drastically:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">go
</span></span><span class="line"><span class="cl">Benchmark_Index-8        47874408    25.14 ns/op    0 B/op    0 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_IndexByte-8    54982188    21.98 ns/op    0 B/op    0 allocs/op
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">c
</span></span><span class="line"><span class="cl">Benchmark_Index          33552540    35.21 ns/op    0 B/op    0 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_IndexByte      36868624    32.81 ns/op    0 B/op    0 allocs/op
</span></span></code></pre></div><p>Still not quite as fast as Go, but it's close. Honestly, I don't know why the <code>memchr</code>-based implementation is still slower than Go's assembly here, but I decided not to pursue it any further.</p>
<p>After running the rest of the <code>strings</code> function benchmarks, the ported versions won all of them except for two:</p>
<table>
<thead>
<tr>
<th>Benchmark</th>
<th style="text-align:right">Go</th>
<th style="text-align:right">C (mimalloc)</th>
<th style="text-align:right">C (arena)</th>
<th>Winner</th>
</tr>
</thead>
<tbody>
<tr>
<td>Clone</td>
<td style="text-align:right">99ns</td>
<td style="text-align:right">42ns</td>
<td style="text-align:right">34ns</td>
<td><strong>C</strong> - 2.4x</td>
</tr>
<tr>
<td>Compare</td>
<td style="text-align:right">47ns</td>
<td style="text-align:right">36ns</td>
<td style="text-align:right">36ns</td>
<td><strong>C</strong> - 1.3x</td>
</tr>
<tr>
<td>Fields</td>
<td style="text-align:right">1524ns</td>
<td style="text-align:right">908ns</td>
<td style="text-align:right">912ns</td>
<td><strong>C</strong> - 1.7x</td>
</tr>
<tr>
<td>Index</td>
<td style="text-align:right">25ns</td>
<td style="text-align:right">35ns</td>
<td style="text-align:right">34ns</td>
<td>Go - 0.7x</td>
</tr>
<tr>
<td>IndexByte</td>
<td style="text-align:right">22ns</td>
<td style="text-align:right">33ns</td>
<td style="text-align:right">33ns</td>
<td>Go - 0.7x</td>
</tr>
<tr>
<td>Repeat</td>
<td style="text-align:right">127ns</td>
<td style="text-align:right">64ns</td>
<td style="text-align:right">67ns</td>
<td><strong>C</strong> - 1.9x</td>
</tr>
<tr>
<td>ReplaceAll</td>
<td style="text-align:right">243ns</td>
<td style="text-align:right">200ns</td>
<td style="text-align:right">203ns</td>
<td><strong>C</strong> - 1.2x</td>
</tr>
<tr>
<td>Split</td>
<td style="text-align:right">1899ns</td>
<td style="text-align:right">1399ns</td>
<td style="text-align:right">1423ns</td>
<td><strong>C</strong> - 1.3x</td>
</tr>
<tr>
<td>ToUpper</td>
<td style="text-align:right">2066ns</td>
<td style="text-align:right">1602ns</td>
<td style="text-align:right">1622ns</td>
<td><strong>C</strong> - 1.3x</td>
</tr>
<tr>
<td>Trim</td>
<td style="text-align:right">501ns</td>
<td style="text-align:right">373ns</td>
<td style="text-align:right">375ns</td>
<td><strong>C</strong> - 1.3x</td>
</tr>
</tbody>
</table>
<p><a href="https://github.com/solod-dev/solod/blob/main/bench/README.md">Benchmarking details</a></p>
<h2 id="optimizing-builder">Optimizing builder</h2>
<p><code>strings.Builder</code> is a common way to compose strings from parts in Go, so I tested its performance too. The results were worse than I expected:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">go
</span></span><span class="line"><span class="cl">Benchmark_WriteS_AutoGrow-8   5385492   224.0 ns/op   1424 B/op   5 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_WriteS_PreGrow-8   10692721   112.9 ns/op    640 B/op   1 allocs/op
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">c
</span></span><span class="line"><span class="cl">Benchmark_WriteS_AutoGrow     5659255   212.9 ns/op   1147 B/op   5 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_WriteS_PreGrow      9811054   122.1 ns/op    592 B/op   1 allocs/op
</span></span></code></pre></div><p>Here, the C version performed about the same as Go, but I expected it to be faster. Unlike <code>Index</code>, <code>Builder</code> is written entirely in Go, so there's no reason the ported version should lose in this benchmark.</p>
<p>The <code>WriteString</code> method looked almost identical in Go and C:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// WriteString appends the contents of s to b&#39;s buffer.
</span></span></span><span class="line"><span class="cl"><span class="c1">// It returns the length of s and a nil error.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">b</span> <span class="o">*</span><span class="nx">Builder</span><span class="p">)</span> <span class="nf">WriteString</span><span class="p">(</span><span class="nx">s</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">b</span><span class="p">.</span><span class="nx">buf</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">buf</span><span class="p">,</span> <span class="nx">s</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">),</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">so_Result</span> <span class="nf">strings_Builder_WriteString</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_String</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">strings_Builder</span><span class="o">*</span> <span class="n">b</span> <span class="o">=</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">strings_Builder_grow</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="nf">so_len</span><span class="p">(</span><span class="n">s</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">b</span><span class="o">-&gt;</span><span class="n">buf</span> <span class="o">=</span> <span class="nf">so_extend</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">b</span><span class="o">-&gt;</span><span class="n">buf</span><span class="p">,</span> <span class="nf">so_string_bytes</span><span class="p">(</span><span class="n">s</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">so_Result</span><span class="p">){.</span><span class="n">val</span><span class="p">.</span><span class="n">as_int</span> <span class="o">=</span> <span class="nf">so_len</span><span class="p">(</span><span class="n">s</span><span class="p">),</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Go's <code>append</code> automatically grows the backing slice, while <code>strings_Builder_grow</code> does it manually (<code>so_extend</code>, on the contrary, doesn't grow the slice — it's merely a <code>memcpy</code> wrapper). So, there shouldn't be any difference. I had to investigate.</p>
<p>Looking at the compiled binary, I noticed a difference in how the functions returned results. Go returns multiple values in separate registers, so <code>(int, error)</code> uses three registers: one for 8-byte <code>int</code>, two for the <code>error</code> interface (implemented as two 8-byte pointers). But in C, <code>so_Result</code> was a single struct made up of two <code>so_Value</code> unions and a <code>so_Error</code> pointer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">union</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">as_bool</span><span class="p">;</span>        <span class="c1">// 1 byte
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_int</span> <span class="n">as_int</span><span class="p">;</span>       <span class="c1">// 8 bytes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">int64_t</span> <span class="n">as_i64</span><span class="p">;</span>      <span class="c1">// 8 bytes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_String</span> <span class="n">as_string</span><span class="p">;</span> <span class="c1">// 16 bytes (ptr + len)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_Slice</span> <span class="n">as_slice</span><span class="p">;</span>   <span class="c1">// 24 bytes (ptr + len + cap)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">void</span><span class="o">*</span> <span class="n">as_ptr</span><span class="p">;</span>        <span class="c1">// 8 bytes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// ... other types
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> <span class="n">so_Value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Value</span> <span class="n">val</span><span class="p">;</span>        <span class="c1">// 24 bytes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_Value</span> <span class="n">val2</span><span class="p">;</span>       <span class="c1">// 24 bytes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_Error</span> <span class="n">err</span><span class="p">;</span>        <span class="c1">// 8 bytes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> <span class="n">so_Result</span><span class="p">;</span>
</span></span></code></pre></div><p>Of course, this 56-byte monster can't be returned in registers — the C calling convention passes it through memory instead. Since <code>WriteString</code> is on the hot path in the benchmark, I figured this had to be the issue. So I switched from a single monolithic <code>so_Result</code> type to signature-specific types for multi-return pairs:</p>
<ul>
<li><code>so_R_bool_err</code> for <code>(bool, error)</code>;</li>
<li><code>so_R_int_err</code> for <code>(so_int, error)</code>;</li>
<li><code>so_R_str_err</code> for <code>(so_String, error)</code>;</li>
<li>etc.</li>
</ul>
<p>Now, the <code>Builder.WriteString</code> implementation in C looked like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">val</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Error</span> <span class="n">err</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">so_R_int_err</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="n">so_R_int_err</span> <span class="nf">strings_Builder_WriteString</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_String</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p><code>so_R_int_err</code> is only 16 bytes — small enough to be returned in two registers. Problem solved! But it wasn't — the benchmark only showed a slight improvement.</p>
<p>After looking into it more, I finally found the real issue: unlike Go, the C compiler wasn't inlining <code>WriteString</code> calls. Adding <code>inline</code> and moving <code>strings_Builder_WriteString</code> to the header file made all the difference:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">go
</span></span><span class="line"><span class="cl">Benchmark_WriteS_AutoGrow-8   5385492   224.0 ns/op   1424 B/op   5 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_WriteS_PreGrow-8   10692721   112.9 ns/op    640 B/op   1 allocs/op
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">c
</span></span><span class="line"><span class="cl">Benchmark_WriteS_AutoGrow    10344024   115.9 ns/op   1147 B/op   5 allocs/op
</span></span><span class="line"><span class="cl">Benchmark_WriteS_PreGrow     41045286    28.74 ns/op   592 B/op   1 allocs/op
</span></span></code></pre></div><p>2-4x faster. That's what I was hoping for!</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>Porting <code>bytes</code> and <code>strings</code> was a mix of easy parts and interesting challenges. The pure functions were straightforward — just translate the syntax and pay attention to operator precedence. The real design challenge was memory management. Using allocators turned out to be a good solution, making memory allocation clear and explicit without being too difficult to use.</p>
<p>The benchmarks showed that the C versions outperformed Go in most cases, sometimes by 2-4x. The only exceptions were <code>Index</code> and <code>IndexByte</code>, where Go relies on hand-written assembly. The <code>strings.Builder</code> optimization was an interesting challenge: what seemed like a return-type issue was actually an inlining problem, and fixing it gave a nice speed boost.</p>
<p>There's a lot more of Go's stdlib to port. In the next post, we'll cover <code>time</code> — a very unique Go package. In the meantime, if you'd like to write Go that translates to C — with no runtime and manual memory management — I invite you to try <a href="https://github.com/solod-dev/solod">Solod</a>. The <code>bytes</code> and <code>strings</code> packages are included, of course.</p>
]]></content:encoded></item><item><title>Porting Go's io package to C</title><link>https://antonz.org/porting-go-io/</link><pubDate>Wed, 25 Mar 2026 14:00:00 +0000</pubDate><guid>https://antonz.org/porting-go-io/</guid><description>Interfaces, slices, multi-returns and alloca.</description><content:encoded><![CDATA[<p>Creating a subset of Go that <a href="/solod/">translates to C</a> was never my end goal. I liked writing C code with Go, but without the standard library it felt pretty limited. So, the next logical step was to port Go's stdlib to C.</p>
<p>Of course, this isn't something I could do all at once. So I started with the standard library packages that had the fewest dependencies, and one of them was the <code>io</code> package. This post is about how that went.</p>
<p><a href="#the-io-package">io package</a> •
<a href="#slices">Slices</a> •
<a href="#multiple-returns">Multiple returns</a> •
<a href="#errors">Errors</a> •
<a href="#interfaces">Interfaces</a> •
<a href="#type-assertion">Type assertion</a> •
<a href="#specialized-readers">Specialized readers</a> •
<a href="#copy">Copy</a> •
<a href="#wrapping-up">Wrapping up</a></p>
<h2 id="the-io-package">The io package</h2>
<p><code>io</code> is one of the core Go packages. It introduces the concepts of <em>readers</em> and <em>writers</em>, which are also common in other programming languages.</p>
<p>In Go, a reader is anything that can read some raw data (bytes) from a source into a slice:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Reader</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">Read</span><span class="p">(</span><span class="nx">p</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>A writer is anything that can take some raw data from a slice and write it to a destination:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Writer</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">Write</span><span class="p">(</span><span class="nx">p</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>io</code> package defines many other interfaces, like <code>Seeker</code> and <code>Closer</code>, as well as combinations like <code>ReadWriter</code> and <code>WriteCloser</code>. It also provides several functions, the most well-known being <code>Copy</code>, which copies all data from a source (represented by a reader) to a destination (represented by a writer):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Copy</span><span class="p">(</span><span class="nx">dst</span> <span class="nx">Writer</span><span class="p">,</span> <span class="nx">src</span> <span class="nx">Reader</span><span class="p">)</span> <span class="p">(</span><span class="nx">written</span> <span class="kt">int64</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</span><span class="p">)</span>
</span></span></code></pre></div><p>C, of course, doesn't have interfaces. But before I get into that, I had to make several other design decisions.</p>
<h2 id="slices">Slices</h2>
<p>In general, a <em>slice</em> is a linear container that holds N elements of type T. Typically, a slice is a view of some underlying data. In Go, a slice consists of a pointer to a block of allocated memory, a length (the number of elements in the slice), and a capacity (the total number of elements that can fit in the backing memory before the runtime needs to re-allocate):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">slice</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">array</span> <span class="nx">unsafe</span><span class="p">.</span><span class="nx">Pointer</span>
</span></span><span class="line"><span class="cl">    <span class="nx">len</span>   <span class="kt">int</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cap</span>   <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Interfaces in the <code>io</code> package work with fixed-length slices (readers and writers should never append to a slice), and they only use byte slices. So, the simplest way to represent this in C could be:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">ptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">size_t</span> <span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">Bytes</span><span class="p">;</span>
</span></span></code></pre></div><p>But since I needed a general-purpose slice type, I decided to do it the Go way instead:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">size_t</span> <span class="n">len</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">size_t</span> <span class="n">cap</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">so_Slice</span><span class="p">;</span>
</span></span></code></pre></div><p>Plus a bound-checking helper to access slice elements:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define so_at(T, s, i) (*so_at_ptr(T, s, i))
</span></span></span><span class="line"><span class="cl"><span class="cp">#define so_at_ptr(T, s, i) ({            \
</span></span></span><span class="line"><span class="cl"><span class="cp">    so_Slice _s_at = (s);                \
</span></span></span><span class="line"><span class="cl"><span class="cp">    size_t _i = (size_t)(i);             \
</span></span></span><span class="line"><span class="cl"><span class="cp">    if (_i &gt;= _s_at.len)                 \
</span></span></span><span class="line"><span class="cl"><span class="cp">        so_panic(&#34;index out of bounds&#34;); \
</span></span></span><span class="line"><span class="cl"><span class="cp">    (T*)_s_at.ptr + _i;                  \
</span></span></span><span class="line"><span class="cl"><span class="cp">})
</span></span></span></code></pre></div><p>Usage example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// go
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">nums</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">int</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">nums</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="p">=</span> <span class="mi">11</span>
</span></span><span class="line"><span class="cl"><span class="nx">nums</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="p">=</span> <span class="mi">22</span>
</span></span><span class="line"><span class="cl"><span class="nx">nums</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="p">=</span> <span class="mi">33</span>
</span></span><span class="line"><span class="cl"><span class="nx">n1</span> <span class="o">:=</span> <span class="nx">nums</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// c
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">so_Slice</span> <span class="n">nums</span> <span class="o">=</span> <span class="nf">so_make_slice</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">so_at</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="n">nums</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">=</span> <span class="mi">11</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nf">so_at</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="n">nums</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">=</span> <span class="mi">22</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nf">so_at</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="n">nums</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="o">=</span> <span class="mi">33</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">so_int</span> <span class="n">n1</span> <span class="o">=</span> <span class="nf">so_at</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="n">nums</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span></code></pre></div><p>So far, so good.</p>
<h2 id="multiple-returns">Multiple returns</h2>
<p>Let's look at the <code>Read</code> method again:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nf">Read</span><span class="p">(</span><span class="nx">p</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</span><span class="p">)</span>
</span></span></code></pre></div><p>It returns two values: an <code>int</code> and an <code>error</code>. C functions can only return one value, so I needed to figure out how to handle this.</p>
<p>The classic approach would be to pass output parameters by pointer, like <code>read(p, &amp;n, &amp;err)</code> or <code>n = read(p, &amp;err)</code>. But that doesn't compose well and looks nothing like Go. Instead, I went with a result struct:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">union</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">as_bool</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">as_int</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int64_t</span> <span class="n">as_i64</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_String</span> <span class="n">as_string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">as_slice</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">as_ptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ... other types
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span> <span class="n">so_Value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Value</span> <span class="n">val</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Error</span> <span class="n">err</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">so_Result</span><span class="p">;</span>
</span></span></code></pre></div><p>The <code>so_Value</code> union can store any primitive type, as well as strings, slices, and pointers. The <code>so_Result</code> type combines a value with an error. So, our <code>Read</code> method (let's assume it's just a regular function for now):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Read</span><span class="p">(</span><span class="nx">p</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</span><span class="p">)</span>
</span></span></code></pre></div><p>Translates to:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">so_Result</span> <span class="nf">Read</span><span class="p">(</span><span class="n">so_Slice</span> <span class="n">p</span><span class="p">);</span>
</span></span></code></pre></div><p>And the caller can access the result like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">so_Result</span> <span class="n">res</span> <span class="o">=</span> <span class="nf">Read</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">res</span><span class="p">.</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_panic</span><span class="p">(</span><span class="n">res</span><span class="p">.</span><span class="n">err</span><span class="o">-&gt;</span><span class="n">msg</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nf">so_println</span><span class="p">(</span><span class="s">&#34;read&#34;</span><span class="p">,</span> <span class="n">res</span><span class="p">.</span><span class="n">val</span><span class="p">.</span><span class="n">as_int</span><span class="p">,</span> <span class="s">&#34;bytes&#34;</span><span class="p">);</span>
</span></span></code></pre></div><h2 id="errors">Errors</h2>
<p>For the error type itself, I went with a simple pointer to an immutable string:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">so_Error_</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">msg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">so_Error_</span><span class="o">*</span> <span class="n">so_Error</span><span class="p">;</span>
</span></span></code></pre></div><p>Plus a constructor macro:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define errors_New(s) (&amp;(struct so_Error_){s})
</span></span></span></code></pre></div><p>I wanted to avoid heap allocations as much as possible, so decided not to support dynamic errors. Only sentinel errors are used, and they're defined at the file level like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">so_Error</span> <span class="n">io_EOF</span> <span class="o">=</span> <span class="nf">errors_New</span><span class="p">(</span><span class="s">&#34;EOF&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">so_Error</span> <span class="n">io_ErrOffset</span> <span class="o">=</span> <span class="nf">errors_New</span><span class="p">(</span><span class="s">&#34;io: invalid offset&#34;</span><span class="p">);</span>
</span></span></code></pre></div><p>Errors are compared by pointer identity (<code>==</code>), not by string content — just like sentinel errors in Go. A <code>nil</code> error is a <code>NULL</code> pointer. This keeps error handling cheap and straightforward.</p>
<h2 id="interfaces">Interfaces</h2>
<p>This was the big one. In Go, an interface is a type that specifies a set of methods. Any concrete type that implements those methods satisfies the interface — no explicit declaration needed. In C, there's no such mechanism.</p>
<p>For interfaces, I decided to use &quot;fat&quot; structs with function pointers. That way, Go's <code>io.Reader</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Reader</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">Read</span><span class="p">(</span><span class="nx">p</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Becomes an <code>io_Reader</code> struct in C:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">so_Result</span> <span class="p">(</span><span class="o">*</span><span class="n">Read</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_Slice</span> <span class="n">p</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">io_Reader</span><span class="p">;</span>
</span></span></code></pre></div><p>The <code>self</code> pointer holds the concrete value, and each method becomes a function pointer that takes <code>self</code> as its first argument. This is less efficient than using a static method table, especially if the interface has a lot of methods, but it's simpler. So I decided it was good enough for the first version.</p>
<p>Now functions can work with interfaces without knowing the specific implementation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// ReadFull reads exactly len(buf) bytes from r into buf.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">so_Result</span> <span class="nf">io_ReadFull</span><span class="p">(</span><span class="n">io_Reader</span> <span class="n">r</span><span class="p">,</span> <span class="n">so_Slice</span> <span class="n">buf</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">n</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Error</span> <span class="n">err</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="nf">so_len</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">err</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">so_Slice</span> <span class="n">curBuf</span> <span class="o">=</span> <span class="nf">so_slice</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">buf</span><span class="p">.</span><span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">so_Result</span> <span class="n">res</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">self</span><span class="p">,</span> <span class="n">curBuf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">err</span> <span class="o">=</span> <span class="n">res</span><span class="p">.</span><span class="n">err</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">n</span> <span class="o">+=</span> <span class="n">res</span><span class="p">.</span><span class="n">val</span><span class="p">.</span><span class="n">as_int</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// A custom reader.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">b</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">reader</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="n">so_Result</span> <span class="nf">reader_Read</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_Slice</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// We&#39;ll read from a string literal.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_String</span> <span class="n">str</span> <span class="o">=</span> <span class="nf">so_str</span><span class="p">(</span><span class="s">&#34;hello world&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">reader</span> <span class="n">rdr</span> <span class="o">=</span> <span class="p">(</span><span class="n">reader</span><span class="p">){.</span><span class="n">b</span> <span class="o">=</span> <span class="nf">so_string_bytes</span><span class="p">(</span><span class="n">str</span><span class="p">)};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Wrap the specific reader into an interface.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">io_Reader</span> <span class="n">r</span> <span class="o">=</span> <span class="p">(</span><span class="n">io_Reader</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">self</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">rdr</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">Read</span> <span class="o">=</span> <span class="n">reader_Read</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Read the first 4 bytes from the string into a buffer.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_Slice</span> <span class="n">buf</span> <span class="o">=</span> <span class="nf">so_make_slice</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ReadFull doesn&#39;t care about the specific reader implementation -
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// it could read from a file, the network, or anything else.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_Result</span> <span class="n">res</span> <span class="o">=</span> <span class="nf">io_ReadFull</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Calling a method on the interface just goes through the function pointer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// r.Read(buf) becomes:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">r</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">self</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span>
</span></span></code></pre></div><h2 id="type-assertion">Type assertion</h2>
<p>Go's interface is more than just a value wrapper with a method table. It also stores type information about the value it holds:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">iface</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">tab</span>  <span class="o">*</span><span class="nx">itab</span>
</span></span><span class="line"><span class="cl">    <span class="nx">data</span> <span class="nx">unsafe</span><span class="p">.</span><span class="nx">Pointer</span>  <span class="c1">// specific value
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">itab</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Inter</span> <span class="o">*</span><span class="nx">InterfaceType</span> <span class="c1">// method table
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">Type</span>  <span class="o">*</span><span class="nx">Type</span>          <span class="c1">// type information
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>Since the runtime knows the exact type inside the interface, it can try to &quot;upgrade&quot; the interface (for example, a regular <code>Reader</code>) to another interface (like <code>WriterTo</code>) using a <em>type assertion</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// copyBuffer copies from src to dst using the provided buffer
</span></span></span><span class="line"><span class="cl"><span class="c1">// until either EOF is reached on src or an error occurs.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">copyBuffer</span><span class="p">(</span><span class="nx">dst</span> <span class="nx">Writer</span><span class="p">,</span> <span class="nx">src</span> <span class="nx">Reader</span><span class="p">,</span> <span class="nx">buf</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="nx">written</span> <span class="kt">int64</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// If the reader has a WriteTo method, use it to do the copy.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="nx">wt</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">src</span><span class="p">.(</span><span class="nx">WriterTo</span><span class="p">);</span> <span class="nx">ok</span> <span class="p">{</span>  <span class="c1">// try &#34;upgrading&#34; to WriterTo
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">return</span> <span class="nx">wt</span><span class="p">.</span><span class="nf">WriteTo</span><span class="p">(</span><span class="nx">dst</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// src is not a WriterTo, proceed with the default copy implementation.
</span></span></span></code></pre></div><p>The last thing I wanted to do was reinvent Go's dynamic type system in C, so dropping this feature was an easy decision.</p>
<p>There's another kind of type assertion, though — when we unwrap the interface to get the value of a specific type:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Does r (a Reader) hold a pointer to a value of concrete type LimitedReader?
</span></span></span><span class="line"><span class="cl"><span class="c1">// If true, lr will get the unwrapped pointer.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">lr</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">r</span><span class="p">.(</span><span class="o">*</span><span class="nx">LimitedReader</span><span class="p">)</span>
</span></span></code></pre></div><p>And this kind of assertion is quite possible in C. All we have to do is compare function pointers:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Are r.Read and LimitedReader_Read the same function?
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">bool</span> <span class="n">ok</span> <span class="o">=</span> <span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">Read</span> <span class="o">==</span> <span class="n">LimitedReader_Read</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">ok</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">io_LimitedReader</span><span class="o">*</span> <span class="n">lr</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>If two different types happened to share the same method implementation, this would break. In practice, each concrete type has its own methods, so the function pointer serves as a reliable type tag.</p>
<h2 id="specialized-readers">Specialized readers</h2>
<p>After I decided on the interface approach, porting the actual <code>io</code> types was pretty easy. For example, <code>LimitedReader</code> wraps a reader and stops with EOF after reading N bytes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">LimitedReader</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">R</span> <span class="nx">Reader</span>
</span></span><span class="line"><span class="cl">    <span class="nx">N</span> <span class="kt">int64</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">l</span> <span class="o">*</span><span class="nx">LimitedReader</span><span class="p">)</span> <span class="nf">Read</span><span class="p">(</span><span class="nx">p</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">l</span><span class="p">.</span><span class="nx">N</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">EOF</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">int64</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">p</span><span class="p">))</span> <span class="p">&gt;</span> <span class="nx">l</span><span class="p">.</span><span class="nx">N</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">p</span> <span class="p">=</span> <span class="nx">p</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="nx">l</span><span class="p">.</span><span class="nx">N</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">n</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">l</span><span class="p">.</span><span class="nx">R</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">p</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">l</span><span class="p">.</span><span class="nx">N</span> <span class="o">-=</span> <span class="nb">int64</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">n</span><span class="p">,</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The logic is straightforward: if there are no bytes left, return EOF. Otherwise, if the buffer is bigger than the remaining size, shorten it. Then, call the underlying reader, and decrease the remaining size.</p>
<p>Here's what the ported C code looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">io_Reader</span> <span class="n">R</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int64_t</span> <span class="n">N</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">io_LimitedReader</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">so_Result</span> <span class="nf">io_LimitedReader_Read</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">so_Slice</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">io_LimitedReader</span><span class="o">*</span> <span class="n">l</span> <span class="o">=</span> <span class="n">self</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">l</span><span class="o">-&gt;</span><span class="n">N</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="n">so_Result</span><span class="p">){.</span><span class="n">val</span><span class="p">.</span><span class="n">as_int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="n">io_EOF</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">((</span><span class="kt">int64_t</span><span class="p">)(</span><span class="nf">so_len</span><span class="p">(</span><span class="n">p</span><span class="p">))</span> <span class="o">&gt;</span> <span class="n">l</span><span class="o">-&gt;</span><span class="n">N</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="nf">so_slice</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">l</span><span class="o">-&gt;</span><span class="n">N</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Result</span> <span class="n">res</span> <span class="o">=</span> <span class="n">l</span><span class="o">-&gt;</span><span class="n">R</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="n">l</span><span class="o">-&gt;</span><span class="n">R</span><span class="p">.</span><span class="n">self</span><span class="p">,</span> <span class="n">p</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">res</span><span class="p">.</span><span class="n">val</span><span class="p">.</span><span class="n">as_int</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">l</span><span class="o">-&gt;</span><span class="n">N</span> <span class="o">-=</span> <span class="p">(</span><span class="kt">int64_t</span><span class="p">)(</span><span class="n">n</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">so_Result</span><span class="p">){.</span><span class="n">val</span><span class="p">.</span><span class="n">as_int</span> <span class="o">=</span> <span class="n">n</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="n">res</span><span class="p">.</span><span class="n">err</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>A bit more verbose, but nothing special. The multiple return values, the interface call with <code>l.R.Read</code>, and the slice handling are all implemented as described in previous sections.</p>
<h2 id="copy">Copy</h2>
<p><code>Copy</code> is where everything comes together. Here's the simplified Go version:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Copy copies from src to dst until either
</span></span></span><span class="line"><span class="cl"><span class="c1">// EOF is reached on src or an error occurs.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="nf">Copy</span><span class="p">(</span><span class="nx">dst</span> <span class="nx">Writer</span><span class="p">,</span> <span class="nx">src</span> <span class="nx">Reader</span><span class="p">)</span> <span class="p">(</span><span class="nx">written</span> <span class="kt">int64</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Allocate a temporary buffer for copying.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">size</span> <span class="o">:=</span> <span class="mi">32</span> <span class="o">*</span> <span class="mi">1024</span>
</span></span><span class="line"><span class="cl">    <span class="nx">buf</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="nx">size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Copy from src to dst using the buffer.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">nr</span><span class="p">,</span> <span class="nx">er</span> <span class="o">:=</span> <span class="nx">src</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">buf</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nx">nr</span> <span class="p">&gt;</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">nw</span><span class="p">,</span> <span class="nx">ew</span> <span class="o">:=</span> <span class="nx">dst</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">buf</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="nx">nr</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="nx">written</span> <span class="o">+=</span> <span class="nb">int64</span><span class="p">(</span><span class="nx">nw</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="nx">ew</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">err</span> <span class="p">=</span> <span class="nx">ew</span>
</span></span><span class="line"><span class="cl">                <span class="k">break</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nx">er</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="nx">er</span> <span class="o">!=</span> <span class="nx">EOF</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">err</span> <span class="p">=</span> <span class="nx">er</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">written</span><span class="p">,</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In Go, <code>Copy</code> allocates its buffer on the heap with <code>make([]byte, size)</code>. I could take a similar approach in C — make <code>Copy</code> take an allocator and use it to create the buffer like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">so_Result</span> <span class="nf">io_Copy</span><span class="p">(</span><span class="n">mem_Allocator</span> <span class="n">a</span><span class="p">,</span> <span class="n">io_Writer</span> <span class="n">dst</span><span class="p">,</span> <span class="n">io_Reader</span> <span class="n">src</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">32</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">buf</span> <span class="o">=</span> <span class="nf">mem_AllocSlice</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>But since this is just a temporary buffer that only exists during the function call, I decided stack allocation was a better choice:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">so_Result</span> <span class="nf">io_Copy</span><span class="p">(</span><span class="n">io_Writer</span> <span class="n">dst</span><span class="p">,</span> <span class="n">io_Reader</span> <span class="n">src</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">8</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Slice</span> <span class="n">buf</span> <span class="o">=</span> <span class="nf">so_make_slice</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p><code>so_make_slice</code> allocates memory on a stack with a bounds-checking macro that wraps C's <code>alloca</code>. It moves the stack pointer and gives you a chunk of memory that's automatically freed when the function returns.</p>
<p>People often avoid using <code>alloca</code> because it can cause a stack overflow, but using a bounds-checking wrapper fixes this issue. Another common concern with <code>alloca</code> is that it's not block-scoped — the memory stays allocated until the function exits. However, since we only allocate once, this isn't a problem.</p>
<p>Here's the simplified C version of <code>Copy</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">so_Result</span> <span class="nf">io_Copy</span><span class="p">(</span><span class="n">io_Writer</span> <span class="n">dst</span><span class="p">,</span> <span class="n">io_Reader</span> <span class="n">src</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_int</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">8</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">;</span> <span class="c1">// smaller buffer, 8 KiB
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">so_Slice</span> <span class="n">buf</span> <span class="o">=</span> <span class="nf">so_make_slice</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">size</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int64_t</span> <span class="n">written</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">so_Error</span> <span class="n">err</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">so_Result</span> <span class="n">resr</span> <span class="o">=</span> <span class="n">src</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="n">src</span><span class="p">.</span><span class="n">self</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">so_int</span> <span class="n">nr</span> <span class="o">=</span> <span class="n">resr</span><span class="p">.</span><span class="n">val</span><span class="p">.</span><span class="n">as_int</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">nr</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">so_Result</span> <span class="n">resw</span> <span class="o">=</span> <span class="n">dst</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="n">dst</span><span class="p">.</span><span class="n">self</span><span class="p">,</span> <span class="nf">so_slice</span><span class="p">(</span><span class="n">so_byte</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">nr</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="n">so_int</span> <span class="n">nw</span> <span class="o">=</span> <span class="n">resw</span><span class="p">.</span><span class="n">val</span><span class="p">.</span><span class="n">as_int</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">written</span> <span class="o">+=</span> <span class="p">(</span><span class="kt">int64_t</span><span class="p">)(</span><span class="n">nw</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">resw</span><span class="p">.</span><span class="n">err</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">err</span> <span class="o">=</span> <span class="n">resw</span><span class="p">.</span><span class="n">err</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">resr</span><span class="p">.</span><span class="n">err</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">resr</span><span class="p">.</span><span class="n">err</span> <span class="o">!=</span> <span class="n">io_EOF</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">err</span> <span class="o">=</span> <span class="n">resr</span><span class="p">.</span><span class="n">err</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">so_Result</span><span class="p">){.</span><span class="n">val</span><span class="p">.</span><span class="n">as_i64</span> <span class="o">=</span> <span class="n">written</span><span class="p">,</span> <span class="p">.</span><span class="n">err</span> <span class="o">=</span> <span class="n">err</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Here, you can see all the parts from this post working together: a function accepting interfaces, slices passed to interface methods, a result type wrapping multiple return values, error sentinels compared by identity, and a stack-allocated buffer used for the copy.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>Porting Go's <code>io</code> package to C meant solving a few problems: representing slices, handling multiple return values, modeling errors, and implementing interfaces using function pointers. None of this needed anything fancy — just structs, unions, functions, and some macros. The resulting C code is more verbose than Go, but it's structurally similar, easy enough to read, and this approach should work well for other Go packages too.</p>
<p>The <code>io</code> package isn't very useful on its own — it mainly defines interfaces and doesn't provide concrete implementations. So, the next two packages to port were naturally <code>bytes</code> and <code>strings</code> — I'll talk about those in the next post.</p>
<p>In the meantime, if you'd like to write Go that translates to C — with no runtime and manual memory management — I invite you to try <a href="https://github.com/solod-dev/solod">Solod</a>. The <code>io</code> package is included, of course.</p>
]]></content:encoded></item></channel></rss>