<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Zig | AkitaOnRails.com</title><link>https://akitaonrails.github.io/en/tags/zig/</link><description>Fabio Akita's blog — tech, career, and assorted geek topics. English edition.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 19 Oct 2026 23:20:52 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/en/tags/zig/index.xml" rel="self" type="application/rss+xml"/><item><title>The Bun in Rust Response Andrew Kelly Should Have Written</title><link>https://akitaonrails.github.io/en/2026/07/09/the-bun-in-rust-response-andrew-kelly-should-have-written/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/07/09/the-bun-in-rust-response-andrew-kelly-should-have-written/</guid><pubDate>Thu, 09 Jul 2026 17:00:00 -0300</pubDate><description>&lt;p&gt;I read Jarred Sumner&amp;rsquo;s official post, &lt;a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener"&gt;&amp;ldquo;Rewriting Bun in Rust&amp;rdquo;&lt;/a&gt;. And I&amp;rsquo;ll start by making my bias clear: I am completely in favor of Bun&amp;rsquo;s decision to rewrite in Rust. Not because Rust won some tribal war against Zig. That&amp;rsquo;s not it. I like Zig a lot. But, for Bun&amp;rsquo;s context, the decision looks understandable, serious, and well executed.&lt;/p&gt;
&lt;p&gt;What I liked most about Jarred&amp;rsquo;s post was the level of detail. He did not sell the idiotic fantasy of writing a prompt like &amp;ldquo;rewrite Bun in Rust&amp;rdquo; and waiting for a miracle. Quite the opposite. What he described was procedure: &lt;code&gt;PORTING.md&lt;/code&gt;, lifetime analysis, &lt;code&gt;LIFETIMES.tsv&lt;/code&gt;, a trial run with three files, dynamic workflows, adversarial reviewers, process correction when the agents started doing stupid things with &lt;code&gt;git stash&lt;/code&gt;, then compiler errors becoming a work queue, then smoke tests, then the whole suite, then multi-platform CI. No irresponsible vibe coding. Engineering using AI as a multiplier.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I read Jarred Sumner&rsquo;s official post, <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">&ldquo;Rewriting Bun in Rust&rdquo;</a>. And I&rsquo;ll start by making my bias clear: I am completely in favor of Bun&rsquo;s decision to rewrite in Rust. Not because Rust won some tribal war against Zig. That&rsquo;s not it. I like Zig a lot. But, for Bun&rsquo;s context, the decision looks understandable, serious, and well executed.</p>
<p>What I liked most about Jarred&rsquo;s post was the level of detail. He did not sell the idiotic fantasy of writing a prompt like &ldquo;rewrite Bun in Rust&rdquo; and waiting for a miracle. Quite the opposite. What he described was procedure: <code>PORTING.md</code>, lifetime analysis, <code>LIFETIMES.tsv</code>, a trial run with three files, dynamic workflows, adversarial reviewers, process correction when the agents started doing stupid things with <code>git stash</code>, then compiler errors becoming a work queue, then smoke tests, then the whole suite, then multi-platform CI. No irresponsible vibe coding. Engineering using AI as a multiplier.</p>
<p>That&rsquo;s why I was disappointed by Andrew Kelley&rsquo;s response, <a href="https://andrewkelley.me/post/my-thoughts-bun-rust-rewrite.html"target="_blank" rel="noopener">&ldquo;My Thoughts on the Bun Rust Rewrite&rdquo;</a>. Andrew is Zig&rsquo;s creator. I wanted to read the best possible technical argument defending Zig. I wanted to see him explain where Bun confused a language problem with an architecture problem. I wanted to see which Zig APIs could have helped, which invariants should have been hardened, where <code>comptime</code>, allocators, arenas, <code>errdefer</code>, <code>DebugAllocator</code>, and Zig&rsquo;s new I/O design could change the analysis.</p>
<p>Instead, a large part of the text becomes an attempt at character assassination. Comments about personality, management, VC, startup culture, backstage gossip. There may even be real resentment there. I don&rsquo;t care. It does not help technically.</p>
<p>So I decided to do the exercise I expected to have read: put Claude Fable 5 and GPT 5.6 Sol as technical arbiters and ask them for the answer Andrew should have written. Not the polite answer. Not the fan-club answer. The technical answer.</p>
<p>Before handing over the microphone, one off-topic note. If you, like me, stared at those beautiful GitHub animations in Bun&rsquo;s post and thought &ldquo;I want that in my projects&rdquo;, I went there and built it. <a href="https://github.com/akitaonrails/github-visualize"target="_blank" rel="noopener"><code>github-visualize</code></a> is a self-hosted dashboard that tracks your repositories and replays commit evolution, the hourly heatmap, and the CI race until everything turns green. It&rsquo;s all open on GitHub.</p>
<p><a href="https://github.com/akitaonrails/github-visualize"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/09/bun-zig-rust/github-visualize-commit-timeline.png" alt="Animated commit timeline in github-visualize"  loading="lazy" /></a></p>
<p>But now let&rsquo;s separate support from proof. One thing is to say &ldquo;I would have done something similar&rdquo;. Another is to say &ldquo;it was technically impossible to continue in Zig&rdquo;. To answer that, we need to look at causes: which bugs were really language bugs, which were architecture problems, which were ecosystem costs, and which could have been reduced with different discipline inside Zig itself.</p>
<p>That&rsquo;s where the analysis starts.</p>
<p>First, minimum vocabulary. <strong>Use-after-free</strong> is using memory after freeing it. <strong>Double-free</strong> is freeing the same memory twice. <strong>Leak</strong> is forgetting to free. <strong>Ownership</strong> is who is responsible for destroying a resource. <strong>Lifetime</strong> is how long a reference remains valid. <strong>FFI</strong> is the boundary where Rust or Zig call C/C++ and lose part of the language guarantees. <strong>GC root</strong> is a way to tell the garbage collector: &ldquo;do not collect this object yet&rdquo;. <strong>Pinning</strong> is holding a buffer in place so it cannot be moved, detached, or invalidated while someone still has a pointer to it. <strong>Reentrancy</strong> is when a call enters the runtime again before the previous call has finished.</p>
<h2>What Rust actually buys<span class="hx:absolute hx:-mt-20" id="what-rust-actually-buys"></span>
    <a href="#what-rust-actually-buys" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The strongest thesis in Bun&rsquo;s post is simple: a large percentage of the listed bugs were use-after-free, double-free, or forgotten resources in error paths. In safe Rust, a good portion of that becomes compiler error or automatic cleanup through <code>Drop</code>, as long as the resource is modeled as an owning value. Rust does not prove the general absence of leaks: <code>Rc</code> cycles, <code>mem::forget</code>, <code>Box::leak</code>, and <code>abort</code> still exist. But it makes accidental leaking in error paths much harder.</p>
<p>That is correct. And it is a strong argument.</p>
<p>Rust&rsquo;s strong point here is not magic. It is that Rust forces certain ownership and lifetime relationships to appear in the type, instead of being scattered around as convention. When a value has an owner, when it is moved, when it leaves scope, when a destructor runs, that stops being just an agreement between programmers. The compiler participates.</p>
<p>In Bun&rsquo;s current code, you can see the pattern in miniature. A MySQL byte wrapper stores a pinned JavaScriptCore value, and <code>Drop</code> undoes the pin when the object dies:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="nb">Drop</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">Bytes</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">drop</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">pinned</span><span class="p">.</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// `pinned` is rooted by the caller&#39;s MarkedArgumentBuffer for the
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// lifetime of this Value (see struct doc); the FFI itself is `safe fn`.
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">JSC__JSValue__unpinArrayBuffer</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">pinned</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// self.slice dropped automatically
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/src/sql_jsc/mysql/MySQLValue.rs#L172-L180"target="_blank" rel="noopener"><code>src/sql_jsc/mysql/MySQLValue.rs</code></a>. The important detail is not the FFI call itself. It is that cleanup follows the value&rsquo;s owner.</p>
<p>This is the round Rust wins cleanly. In a small project, discipline is enough. In half a million lines, with reentrant callbacks, threadpool, libuv, sockets, TLS, JavaScriptCore, parser, bundler, package manager, and Node API, discipline becomes recurring cost.</p>
<p>Rust probably reduced the invalid state space the team needed to keep in their heads. Real win.</p>
<p>But a real win does not mean total victory.</p>
<h2>Zig was not defenseless<span class="hx:absolute hx:-mt-20" id="zig-was-not-defenseless"></span>
    <a href="#zig-was-not-defenseless" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The weakest argument you can make against Zig is pretending it is C with new syntax. It is not. Zig has explicit mechanisms to address exactly part of these problems.</p>
<p>The most obvious one is <a href="https://ziglang.org/documentation/master/#errdefer"target="_blank" rel="noopener"><code>errdefer</code></a>, the idiomatic mechanism for cleanup on error paths:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">readThing</span><span class="p">(</span><span class="n">alloc</span><span class="p">:</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">mem</span><span class="p">.</span><span class="n">Allocator</span><span class="p">,</span><span class="w"> </span><span class="n">fail</span><span class="p">:</span><span class="w"> </span><span class="kt">bool</span><span class="p">)</span><span class="w"> </span><span class="o">!</span><span class="p">[]</span><span class="kt">u8</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">buf</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">alloc</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="mi">16</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">errdefer</span><span class="w"> </span><span class="n">alloc</span><span class="p">.</span><span class="nf">free</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">fail</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="k">error</span><span class="p">.</span><span class="n">Boom</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">buf</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">test</span><span class="w"> </span><span class="s">&#34;leak em caminho de erro&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">alloc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="n">allocator</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectError</span><span class="p">(</span><span class="k">error</span><span class="p">.</span><span class="n">Boom</span><span class="p">,</span><span class="w"> </span><span class="nf">readThing</span><span class="p">(</span><span class="n">alloc</span><span class="p">,</span><span class="w"> </span><span class="kc">true</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><a href="https://ziglang.org/documentation/master/#Report-Memory-Leaks"target="_blank" rel="noopener"><code>std.testing.allocator</code> reports leaks</a> in tests. <a href="https://github.com/ziglang/zig/blob/master/lib/std/heap/debug_allocator.zig"target="_blank" rel="noopener"><code>DebugAllocator</code></a> helps find double-free, leaks, and many invalid memory uses, including making bugs noisier by not immediately reusing addresses. This is not the same thing as a static proof of the absence of use-after-free. It is not a borrow checker. It does not cover allocation done inside C++, JavaScriptCore, BoringSSL, or any other external allocator. It only catches what goes through it and only on the paths exercised by tests.</p>
<p>The allocator&rsquo;s own comment is precise about what it provides:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="cs">//! * Captures stack traces on allocation, free, and optionally resize.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cs">//! * Double free detection, which prints all three traces (first alloc, first</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cs">//!   free, second free).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cs">//! * Leak detection, with stack traces.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cs">//! * Never reuses memory addresses, making it easier for Zig to detect branch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cs">//!   on undefined values in case of dangling pointers.</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/ziglang/zig/blob/master/lib/std/heap/debug_allocator.zig#L3-L12"target="_blank" rel="noopener"><code>debug_allocator.zig</code></a>. It is a good testing and debugging tool. It is not a theorem.</p>
<p>Even so, it is wrong to say Zig had no tools. It did.</p>
<p>The difference is feedback timing. In Rust, a lot appears at compile time. In Zig, a lot appears in tests, fuzzing, instrumented allocators, ASAN, review, and architectural style. In critical code, that matters. Early feedback is better.</p>
<p>But it also matters to say the whole sentence: if the problem was lack of clear invariants in the Zig code, the answer could have been a rewrite in Rust. It could also have been an aggressive restructuring of the Zig.</p>
<p>We do not know which would have been cheaper. We only know which one Bun chose.</p>
<h2><code>Drop</code> is not just sugar, but it is not a miracle either<span class="hx:absolute hx:-mt-20" id="drop-is-not-just-sugar-but-it-is-not-a-miracle-either"></span>
    <a href="#drop-is-not-just-sugar-but-it-is-not-a-miracle-either" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Bun&rsquo;s post is right to contrast <code>defer</code> with <code>Drop</code>.</p>
<p>In Zig, you write:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="kr">var</span><span class="w"> </span><span class="n">resource</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">Resource</span><span class="p">.</span><span class="nf">init</span><span class="p">(</span><span class="n">allocator</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="n">resource</span><span class="p">.</span><span class="nf">deinit</span><span class="p">();</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This is explicit. It is in your face. There is no hidden control flow. That is Zig&rsquo;s philosophy: if something happens, you should be able to point to where it happens.</p>
<p>In Rust, if the type implements <code>Drop</code>, cleanup follows the owner:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="nb">Drop</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">Resource</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">drop</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">deinit</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This is not just syntactic sugar. <a href="https://doc.rust-lang.org/reference/destructors.html"target="_blank" rel="noopener">Rust&rsquo;s destruction rules</a> connect <code>Drop</code> to ownership, move semantics, field destruction order, and panic unwind. Ordinary safe code cannot use a value after it has been moved, nor double-drop the object as if it still owned it. It is a language guarantee.</p>
<p>But the guarantee ends where semantics the compiler does not understand begin.</p>
<p>If <code>drop</code> calls an FFI function to unprotect a JavaScriptCore value, Rust guarantees the destructor will run on normal scope and drop paths, not on <code>abort</code>, <code>mem::forget</code>, intentional leaks, or <code>Rc</code> cycles. It does not prove that the JSC pointer is still valid. It does not prove the GC has no special rule. It does not prove an asynchronous C callback will not call back after you destroyed the wrapper.</p>
<p>The comment in the same Bun wrapper shows the size of the contract that still remains outside the compiler:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="sd">/// JS could `transfer()`/detach an earlier ArrayBuffer, or drop the last
</span></span></span><span class="line"><span class="cl"><span class="sd">/// JS reference to it and force GC, while we still hold a borrowed slice
</span></span></span><span class="line"><span class="cl"><span class="sd">/// into it. Pinning the backing `ArrayBuffer` makes it non-detachable for
</span></span></span><span class="line"><span class="cl"><span class="sd">/// the duration, and the caller&#39;s stack-scoped `MarkedArgumentBuffer`
</span></span></span><span class="line"><span class="cl"><span class="sd">/// roots the wrapper so GC can&#39;t sweep the cell. `Drop` unpins.
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Bytes</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">slice</span>: <span class="nc">ZigStringSlice</span><span class="p">,</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/src/sql_jsc/mysql/MySQLValue.rs#L142-L154"target="_blank" rel="noopener"><code>MySQLValue.rs</code></a>. Notice the size of the contract. It did not disappear because this is Rust. It became documented and encapsulated.</p>
<p>That is the real boundary: Rust improves the Rust side of the contract. It does not perform magic inside the JavaScriptCore heap.</p>
<h2>The FFI boundary is still hell<span class="hx:absolute hx:-mt-20" id="the-ffi-boundary-is-still-hell"></span>
    <a href="#the-ffi-boundary-is-still-hell" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Bun is not a simple CLI. Bun is a JavaScript runtime. It talks to JavaScriptCore, uWebSockets, usockets, BoringSSL, SQLite, compression libraries, system APIs, asynchronous handles, buffers that JavaScript can detach in the middle of a coercion, reentrant callbacks, and a conservative GC.</p>
<p>That is where the debate gets interesting.</p>
<p>Inside an <code>unsafe</code> block, Rust requires discipline. The <a href="https://doc.rust-lang.org/nomicon/ffi.html"target="_blank" rel="noopener">Rustonomicon on FFI</a> itself treats foreign calls and raw pointers as programmer obligations. If the binding is lying, if the real ownership does not match the type, if the foreign callback outlives the Rust object, the compiler does not save you.</p>
<p>Bun&rsquo;s binding documents the preconditions in the declaration itself:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">unsafe</span><span class="w"> </span><span class="k">extern</span><span class="w"> </span><span class="s">&#34;C&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="sd">/// By-value `JSValue`; C++ side null-checks and reads its own heap state.
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="sd">/// No caller-side preconditions → `safe fn`.
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">safe</span><span class="w"> </span><span class="k">fn</span> <span class="nf">JSC__JSValue__unpinArrayBuffer</span><span class="p">(</span><span class="n">v</span>: <span class="nc">JSValue</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="sd">/// 0 = detached/null, 1 = FastTypedArray (GC-movable — caller should dupe;
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="sd">/// no unpin needed), 2 = pinned ArrayBuffer (caller must unpin).
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/src/sql_jsc/mysql/MySQLValue.rs#L859-L868"target="_blank" rel="noopener"><code>MySQLValue.rs</code></a>. The language lets you declare a foreign call safe. The obligation to prove that safety remains ours.</p>
<p>But there is an important difference: Rust lets you encapsulate that <code>unsafe</code> behind safe APIs. You can create handle types, <code>NonNull</code>, <code>PhantomData</code>, <code>Pin</code>, lifetimes marking the relationship between wrapper and runtime, wrappers that do not implement <code>Send</code> or <code>Sync</code>, RAII for GC roots, and a smaller surface for audit. The goal is not to eliminate danger. It is to shrink the place where danger lives.</p>
<p>The most didactic example in Bun&rsquo;s post is the <code>uv_close</code> bug. The first version compiled, but handed a <code>Box</code> to libuv to close asynchronously and let the <code>Box</code> fall at the end of the scope. Use-after-free followed by double-free. The fixed version in the current code makes the transfer explicit:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="n">spawn</span>::<span class="n">WindowsStdioResult</span>::<span class="n">Buffer</span><span class="p">(</span><span class="n">pipe</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// `uv_close` is async — libuv keeps the raw handle pointer until the
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// next loop tick. Leak the Box so it outlives this scope; dropping it
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// here would be a use-after-free + double-free when the callback fires.
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Box</span>::<span class="n">leak</span><span class="p">(</span><span class="n">pipe</span><span class="p">).</span><span class="n">close</span><span class="p">(</span><span class="n">Subprocess</span>::<span class="n">on_pipe_close</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/src/runtime/api/bun/js_bun_spawn_bindings.rs#L1359-L1366"target="_blank" rel="noopener"><code>js_bun_spawn_bindings.rs</code></a>. It compiled in Rust and was still wrong at the C/async boundary. The review process caught it.</p>
<p>Here <code>leak</code> does not mean &ldquo;leak forever&rdquo;. It means removing the <code>Box</code> from Rust&rsquo;s lexical control and transferring ownership to libuv&rsquo;s asynchronous protocol, whose callback later reconstructs and frees the allocation.</p>
<p>Zig also lets you write good wrappers. The difference is that it does not have the <code>safe</code>/<code>unsafe</code> boundary as part of the language. In Zig, everything is explicit and reviewable. In Rust, a larger part of the contract can become type.</p>
<p>For a large team, that matters. It matters even more when part of the code is being generated by agents.</p>
<h2>Arenas: yes, Bun already used them<span class="hx:absolute hx:-mt-20" id="arenas-yes-bun-already-used-them"></span>
    <a href="#arenas-yes-bun-already-used-them" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Here the pro-Zig argument needs to be honest. Arenas are idiomatic and excellent for certain problems: parser, AST, request lifecycle, data that dies together.</p>
<p>But Bun&rsquo;s post does not ignore arenas. It explicitly says the existing approach was already a mix of lifetimes by arena, reference counting, and &ldquo;pay really close attention&rdquo;. It even cites parser state and AST nodes as good cases for an arena.</p>
<p>The excerpt from the post is this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Our current approach is a mix of:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">- arena lifetimes, where the scope of when it&#39;s accessible is clear
</span></span><span class="line"><span class="cl">  (parser state doesn&#39;t escape the calling function and so AST nodes
</span></span><span class="line"><span class="cl">  are a good choice there)
</span></span><span class="line"><span class="cl">- reference-counting
</span></span><span class="line"><span class="cl">- pay really close attention</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">Bun&rsquo;s official post</a>. The good counter-argument is not &ldquo;use arena&rdquo;. They already did. The good counter-argument is asking whether the remaining boundaries could have been hardened in Zig.</p>
<p>So the correct response is not &ldquo;you forgot arenas&rdquo;. They did not forget.</p>
<p>The correct response is: arena solves the case where lifetime is clear. It does not, by itself, solve a socket handle that closes asynchronously, a callback that runs later, an SSL session that needs a specific <code>free</code>, a GC root, a pointer that may be invalidated by reentrancy, or an object that crosses several API layers with implicit ownership.</p>
<p>Arena is a good tool. It is not a religion.</p>
<p>If Bun had hardened the boundaries more in Zig, maybe part of the rewrite&rsquo;s benefit could have come from that: more restricted APIs, fewer raw pointers crossing subsystems, handles with explicit ownership, allocators instrumented by default in tests, fuzzing aimed at reentrancy, wrappers that force <code>deinit</code>, less &ldquo;pay attention&rdquo;.</p>
<p>But that would also be a rewrite. Just inside Zig.</p>
<h2>The borrow checker helps, but not where the internet thinks<span class="hx:absolute hx:-mt-20" id="the-borrow-checker-helps-but-not-where-the-internet-thinks"></span>
    <a href="#the-borrow-checker-helps-but-not-where-the-internet-thinks" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A lot of people talk about the borrow checker as if it were a universal bug detector. It is not. The borrow checker is the part of the Rust compiler that prevents certain invalid borrows: two mutable owners at the same time, a live reference after the owner dies, use after move. That is a lot. It is not everything.</p>
<p>It does not understand the JavaScriptCore heap. It does not understand every GC rule. It does not automatically understand that a C callback can trigger JS, that JS can detach an <code>ArrayBuffer</code>, that it can reenter the runtime, that it can invalidate a pointer captured earlier. These invariants need to be modeled.</p>
<p>But when they are modeled, Rust charges you.</p>
<p>A small example in Bun: a <code>uSockets</code> wrapper that turns a C pointer into a Rust borrow tied to <code>&amp;mut self</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">PacketBuffer</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">get_peer</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">index</span>: <span class="nc">c_int</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kp">&amp;</span><span class="nc">mut</span><span class="w"> </span><span class="n">sockaddr_storage</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// SAFETY: uSockets guarantees a non-null, properly-aligned peer pointer for
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// indices &lt; packet count. The returned storage lives inside the C-owned packet
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// buffer, which is exclusively loaned to the data callback for its duration; no
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// other Rust or C path holds a reference to it. The reborrow of `&amp;mut self`
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ties the returned lifetime to this handle, so the borrow checker prevents
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// obtaining a second overlapping `&amp;mut` via `get_peer`/`get_payload`.
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="o">*</span><span class="n">us_udp_packet_buffer_peer</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">index</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/src/uws_sys/udp.rs#L200-L209"target="_blank" rel="noopener"><code>src/uws_sys/udp.rs</code></a>. The <code>unsafe</code> still exists. But after the wrapper creates the reference, the borrow checker can prevent certain abuses on the Rust side.</p>
<p>And Bun is not only JavaScript heap. It is parser, bundler, module resolver, package manager, file watcher, HTTP protocol, buffers, caches, tables, queues, process state, glue code. In these parts, local ownership matters a lot. <code>Vec</code>, <code>String</code>, slices, maps, wrappers, enums, finite states: all of that becomes easier to refactor with the compiler slapping your hand.</p>
<p>The honest argument is not &ldquo;the borrow checker solves Bun&rdquo;. It is: the borrow checker reduces many classes of error on Bun&rsquo;s Rust side, and that frees human attention for the part that remains hard.</p>
<p>That is strong enough. No need to exaggerate.</p>
<h2><code>comptime</code>: here Zig has a real advantage<span class="hx:absolute hx:-mt-20" id="comptime-here-zig-has-a-real-advantage"></span>
    <a href="#comptime-here-zig-has-a-real-advantage" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The best technical counterexample in Bun&rsquo;s own post is <code>Output.pretty</code>.</p>
<p>In Zig, the format string could be a <code>comptime</code> parameter. Bun&rsquo;s own post puts the difference like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// Zig:
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="n">inline</span><span class="w"> </span><span class="k">fn</span> <span class="nf">pretty</span><span class="p">(</span><span class="n">comptime</span><span class="w"> </span><span class="n">fmt</span>: <span class="nc">string</span><span class="p">,</span><span class="w"> </span><span class="n">args</span>: <span class="nc">anytype</span><span class="p">)</span><span class="w"> </span><span class="n">void</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Output</span><span class="p">.</span><span class="n">pretty</span><span class="p">(</span><span class="s">&#34;&lt;r&gt;{f}&lt;r&gt;&#34;</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="n">hyperlink</span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Primeira API Rust:
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">pretty</span><span class="p">(</span><span class="n">payload</span>: <span class="nc">impl</span><span class="w"> </span><span class="n">PrettyFmtInput</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Output</span>::<span class="n">pretty</span><span class="p">(</span><span class="fm">format_args!</span><span class="p">(</span><span class="s">&#34;&lt;r&gt;</span><span class="si">{}</span><span class="s">&lt;r&gt;&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">hyperlink</span><span class="p">));</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This allowed processing color and formatting markers in the literal known at compile time, before mixing runtime arguments. When porting to Rust, the first version became a function. Result: the tag parser touched bytes that came from the arguments. Issue <a href="https://github.com/oven-sh/bun/issues/30693"target="_blank" rel="noopener">#30693</a> shows the regression in port/canary involving an OSC 8 hyperlink escape and <code>&lt;r&gt;</code>, not a failure released in stable.</p>
<p>The bug was exactly the contamination between template and runtime argument:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">TerminalHyperlink::Display emits an OSC 8 hyperlink:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">\x1b]8;;URL\x1b\ai\x1b]8;;\x1b\
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The closing string terminator ST is the final two bytes `\x1b\`.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">This is then wrapped via `Output::pretty(format_args!(&#34;&lt;r&gt;{}&lt;r&gt;&#34;, hyperlink))`.
</span></span><span class="line"><span class="cl">The rendered string tail is `...\x1b\&lt;r&gt;`.</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/issues/30693"target="_blank" rel="noopener">issue #30693</a>.</p>
<p>Then they fixed it using a macro.</p>
<p>The current code makes the difference explicit:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="sd">/// Write to stdout with `&lt;tag&gt;` color expansion.
</span></span></span><span class="line"><span class="cl"><span class="sd">/// Function form: performs the `&lt;tag&gt;` → ANSI rewrite at runtime on the rendered
</span></span></span><span class="line"><span class="cl"><span class="sd">/// payload (using stdout&#39;s colour state). Prefer the `pretty!` macro for literal
</span></span></span><span class="line"><span class="cl"><span class="sd">/// templates so the rewrite happens at compile time.
</span></span></span><span class="line"><span class="cl"><span class="cp">#[inline(always)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">pretty</span><span class="p">(</span><span class="n">payload</span>: <span class="nc">impl</span><span class="w"> </span><span class="n">PrettyFmtInput</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">buf</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">payload</span><span class="p">.</span><span class="n">into_pretty_buf</span><span class="p">(</span><span class="n">enable_ansi_colors_stdout</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">write_bytes</span><span class="p">(</span><span class="n">Destination</span>::<span class="n">Stdout</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">buf</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And right below:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="sd">/// Compile-time `&lt;tag&gt;` → ANSI escape rewriter.
</span></span></span><span class="line"><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="cl"><span class="sd">/// This must be a proc-macro because it consumes a string literal and emits
</span></span></span><span class="line"><span class="cl"><span class="sd">/// a new string literal usable as a `format_args!` template.
</span></span></span><span class="line"><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="cl"><span class="sd">/// `pretty_fmt!(&#34;&lt;red&gt;{s}&lt;r&gt;&#34;, true)`  → `&#34;\x1b[31m{}\x1b[0m&#34;`
</span></span></span><span class="line"><span class="cl"><span class="sd">/// `pretty_fmt!(&#34;&lt;red&gt;{s}&lt;r&gt;&#34;, false)` → `&#34;{}&#34;`
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">use</span><span class="w"> </span><span class="n">bun_core_macros</span>::<span class="n">pretty_fmt</span><span class="p">;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/src/bun_core/output.rs#L1765-L1805"target="_blank" rel="noopener"><code>src/bun_core/output.rs</code></a>.</p>
<p>This demonstrates a legitimate strength of Zig: <code>comptime</code> is the language itself running at compile time. You do not switch to a parallel system. In Rust, macros are powerful and mature, but they are another mechanism. For some kinds of API, Zig is simply more direct.</p>
<p>But let&rsquo;s not turn a porting bug into universal proof against Rust. The problem there was not &ldquo;Rust is fragile&rdquo;. It was the wrong translation of a <code>comptime</code> API into a runtime function. Bun&rsquo;s own post recognizes that and fixes it with a macro.</p>
<p>The correct reading is simpler: Zig had a structural guarantee there. The first Rust version lost that guarantee. That cost a regression in port/canary.</p>
<p>Point for Zig.</p>
<h2>Binary size and performance: careful with cheap causality<span class="hx:absolute hx:-mt-20" id="binary-size-and-performance-careful-with-cheap-causality"></span>
    <a href="#binary-size-and-performance-careful-with-cheap-causality" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Bun&rsquo;s post shows good numbers: smaller binary, some faster benchmarks, better startup in certain scenarios.</p>
<p>The numbers from the post:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Bun v1.4.0 (canary) — Windows — 76 MB
</span></span><span class="line"><span class="cl">Bun v1.3.14          — Windows — 94 MB
</span></span><span class="line"><span class="cl">Bun v1.4.0 (canary) — Linux   — 70 MB
</span></span><span class="line"><span class="cl">Bun v1.3.14          — Linux   — 88 MB
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Bun.serve  — 169.6k → 177.7k — +4.8%
</span></span><span class="line"><span class="cl">node:http  — 103.8k → 108.5k — +4.5%
</span></span><span class="line"><span class="cl">fastify    —  91.5k →  95.9k — +4.8%</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">Bun&rsquo;s official post</a>.</p>
<p>I believe the numbers. I do not believe in simple attribution.</p>
<p>A rewrite changes many things at the same time. It changes code layout, removes debt, changes toolchain, touches LTO, changes how generics/comptime are instantiated, changes linker, changes dead code, changes ICU, changes internal organization. When all of that happens together, you cannot look at the graph and say: &ldquo;it was Rust&rdquo;.</p>
<p>You also cannot say: &ldquo;it was only the rewrite&rdquo;.</p>
<p>The post itself talks about a combination: Rust, ICU changes, Identical Code Folding, LTO, codegen, accumulated cleanup. LTO is optimization at the final link stage, when the compiler sees more of the program before generating the binary. Identical Code Folding is the linker merging identical functions into one. <a href="https://andrewkelley.me/post/my-thoughts-bun-rust-rewrite.html"target="_blank" rel="noopener">Andrew is right</a> to point out that Zig also supports LTO and that some of these gains could have been pursued before. But that does not prove the result would be the same. It only proves causality is mixed.</p>
<p>Bun&rsquo;s <code>Cargo.toml</code> shows there is explicit build engineering in this story:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># Cargo&#39;s defaults (lto=false, codegen-units=16) leave us at ~105 crates × 16</span>
</span></span><span class="line"><span class="cl"><span class="c"># CGUs ≈ 1680 separately-optimized units with NO cross-crate inlining beyond</span>
</span></span><span class="line"><span class="cl"><span class="c"># `#[inline]`-annotated leaf fns. `lto = &#34;fat&#34;` + `codegen-units = 1` collapses</span>
</span></span><span class="line"><span class="cl"><span class="c"># the whole Rust crate graph into one LLVM module, matching Zig&#39;s shape.</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">profile</span><span class="p">.</span><span class="nx">release</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">lto</span> <span class="p">=</span> <span class="s2">&#34;fat&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">codegen-units</span> <span class="p">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="nx">debug</span> <span class="p">=</span> <span class="s2">&#34;line-tables-only&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">strip</span> <span class="p">=</span> <span class="s2">&#34;none&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">panic</span> <span class="p">=</span> <span class="s2">&#34;abort&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/Cargo.toml#L117-L135"target="_blank" rel="noopener"><code>Cargo.toml</code></a>.</p>
<p>And there is LTO configuration crossing Rust and C++:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Cross-language LTO: rustc emits LLVM bitcode (`-Clinker-plugin-lto`) into
</span></span></span><span class="line"><span class="cl"><span class="cm"> * `libbun_rust.a` so the final lld `-flto=thin` link sees through Rust↔C++
</span></span></span><span class="line"><span class="cl"><span class="cm"> * call edges. When false but `lto` is true, both halves still LTO
</span></span></span><span class="line"><span class="cl"><span class="cm"> * independently (C++ via `-flto=thin`, Rust via `[profile.release] lto =
</span></span></span><span class="line"><span class="cl"><span class="cm"> * &#34;fat&#34;`); only the cross-language inlining is lost.
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="nx">crossLangLto</span>: <span class="kt">boolean</span><span class="p">;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/scripts/build/config.ts#L118-L129"target="_blank" rel="noopener"><code>scripts/build/config.ts</code></a>. It is not &ldquo;Rust won&rdquo;. It is language, toolchain, linker, configuration, and code working together.</p>
<p>Same thing for stack usage and parser depth. The post attributes improvement to Rust emitting better <a href="https://llvm.org/docs/LangRef.html#llvm-lifetime-start-intrinsic"target="_blank" rel="noopener"><code>llvm.lifetime.start/end</code></a> and allowing stack slot reuse. Codegen detail? Yes. But a codegen detail delivered by today&rsquo;s toolchain counts. Zig itself still has an <a href="https://github.com/ziglang/zig/issues/23475"target="_blank" rel="noopener">open issue about stack allocations that do not disappear at the end of scope</a>. For the user, it matters little whether the advantage comes from the pure language, the compiler, or backend maturity. What matters is that it works today.</p>
<p>The mechanism described by Bun is this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Rust&#39;s LLVM IR codegen emits LLVM&#39;s llvm.lifetime.start and
</span></span><span class="line"><span class="cl">llvm.lifetime.end intrinsics for stack variables when they are no longer
</span></span><span class="line"><span class="cl">in use, which lets LLVM reuse stack space slots.</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And the Zig ticket says, bluntly, that this case is still not solved:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Block stack allocations do not disappear at end of block scope #23475</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>There is also the architectural solution: iterative parser with an explicit stack on the heap. Works in Zig and Rust. But it is more work.</p>
<p>Honest reading: the numbers favor Bun&rsquo;s Rust version in that experiment. They do not prove general language superiority.</p>
<h2>Miri, crates, and the real world<span class="hx:absolute hx:-mt-20" id="miri-crates-and-the-real-world"></span>
    <a href="#miri-crates-and-the-real-world" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>There are Rust advantages that have no direct equivalent in Zig today.</p>
<p>Crates is the obvious one. The Rust ecosystem is large, mature, and practical. For a project the size of Bun, a good off-the-shelf library is real engineering value. Zig is still maturing on this point.</p>
<p><a href="https://github.com/rust-lang/miri"target="_blank" rel="noopener"><code>Miri</code></a> is also a real advantage. It detects classes of undefined behavior in executed Rust code: out-of-bounds, use-after-free, uninitialized data, alignment problems, data races in certain cases. The project itself documents the limits: it is not a formal proof, it evaluates the executions you provide, and it has restricted support for FFI and platform APIs. It does not replace testing. But to validate isolated <code>unsafe</code> abstractions, it is a tool Zig does not have in the same way.</p>
<p>Bun does not merely cite Miri in the abstract. It is in the toolchain:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">toolchain</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">channel</span> <span class="p">=</span> <span class="s2">&#34;nightly-2026-05-06&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">components</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;rust-src&#34;</span><span class="p">,</span> <span class="s2">&#34;rustfmt&#34;</span><span class="p">,</span> <span class="s2">&#34;clippy&#34;</span><span class="p">,</span> <span class="s2">&#34;miri&#34;</span><span class="p">,</span> <span class="s2">&#34;llvm-tools&#34;</span><span class="p">]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/rust-toolchain.toml#L1-L24"target="_blank" rel="noopener"><code>rust-toolchain.toml</code></a>.</p>
<p>And there is an internal test explaining exactly the use:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// Run under Miri (`bun run rust:miri -p bun_ptr`): every path here walks raw
</span></span></span><span class="line"><span class="cl"><span class="c1">// pointers through `Box::into_raw` / `heap::take`, so Tree Borrows is what
</span></span></span><span class="line"><span class="cl"><span class="c1">// proves the ref/deref/destructor handoff does not alias or use-after-free.
</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://github.com/oven-sh/bun/blob/fc865b398e51de8a95ddde4bca36408b26d40884/src/ptr/ref_count.rs#L1313-L1315"target="_blank" rel="noopener"><code>src/ptr/ref_count.rs</code></a>. It does not prove the whole program. It exercises a dangerous abstraction under a model made to detect undefined behavior on those executed paths.</p>
<p>There is also hiring, tooling, documentation, IDE, review culture, CI, examples, people in the market. This is not a political detail. It is engineering. A language exists inside an ecosystem.</p>
<p>If Bun were a smaller project, with a smaller team, less pressure, and less Node-compatible surface area, maybe I would weigh this differently. But Bun became a runtime used by millions of monthly downloads. The margin for &ldquo;let&rsquo;s maintain perfect discipline&rdquo; shrinks.</p>
<h2>Async: Zig may have a better answer, but it is not Bun&rsquo;s present yet<span class="hx:absolute hx:-mt-20" id="async-zig-may-have-a-better-answer-but-it-is-not-buns-present-yet"></span>
    <a href="#async-zig-may-have-a-better-answer-but-it-is-not-buns-present-yet" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Zig removed the old async/await and is <a href="https://kristoff.it/blog/zig-new-async-io/"target="_blank" rel="noopener">redesigning I/O around <code>std.Io</code></a>, passed by the caller in a way similar to <code>Allocator</code>. The idea is good: avoid classic function coloring, make the function less coupled to blocking, threaded, or event-driven execution, and treat I/O as an explicit capability.</p>
<p>The proposed format is like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="kr">const</span><span class="w"> </span><span class="n">std</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">@import</span><span class="p">(</span><span class="s">&#34;std&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="w"> </span><span class="n">Io</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">Io</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span><span class="w"> </span><span class="nf">saveData</span><span class="p">(</span><span class="n">io</span><span class="p">:</span><span class="w"> </span><span class="n">Io</span><span class="p">,</span><span class="w"> </span><span class="n">data</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kr">const</span><span class="w"> </span><span class="kt">u8</span><span class="p">)</span><span class="w"> </span><span class="o">!</span><span class="kt">void</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">file</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">Io</span><span class="p">.</span><span class="n">Dir</span><span class="p">.</span><span class="nf">cwd</span><span class="p">().</span><span class="nf">createFile</span><span class="p">(</span><span class="n">io</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;save.txt&#34;</span><span class="p">,</span><span class="w"> </span><span class="p">.{});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="n">file</span><span class="p">.</span><span class="nf">close</span><span class="p">(</span><span class="n">io</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">file</span><span class="p">.</span><span class="nf">writeAll</span><span class="p">(</span><span class="n">io</span><span class="p">,</span><span class="w"> </span><span class="n">data</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">out</span><span class="p">:</span><span class="w"> </span><span class="n">Io</span><span class="p">.</span><span class="n">File</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">.</span><span class="nf">stdout</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">out</span><span class="p">.</span><span class="nf">writeAll</span><span class="p">(</span><span class="n">io</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;save complete&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://kristoff.it/blog/zig-new-async-io/"target="_blank" rel="noopener">Kristoff on Zig&rsquo;s new async I/O</a>. <code>io</code> is a capability passed explicitly, like <code>Allocator</code>.</p>
<p>I like this direction a lot.</p>
<p>But it is still in transition. The <a href="https://github.com/ziglang/zig/issues/23446"target="_blank" rel="noopener">tracking issue</a> remains open: there is a prototype, roadmap, discussion, parts landing gradually. It is not a stable foundation Bun could bet on today without cost.</p>
<p>So the argument here cannot be &ldquo;Zig already solved it&rdquo;. The correct formulation is: Zig is moving toward an async/I/O design that may be cleaner in some aspects than the traditional colored model. But Bun needed to make a decision with what exists now, not with what promises to exist later.</p>
<p>That is always cruel to young languages. But it is real.</p>
<h2>The rewrite process matters more than the language<span class="hx:absolute hx:-mt-20" id="the-rewrite-process-matters-more-than-the-language"></span>
    <a href="#the-rewrite-process-matters-more-than-the-language" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The best part of Jarred&rsquo;s post is not Rust. It is method.</p>
<p>The mental loop of the post is literally this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Pseudocode, not real code:
</span></span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nx">task</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="p">((</span><span class="nx">task</span> <span class="o">=</span> <span class="nx">todoList</span><span class="p">.</span><span class="nx">pop</span><span class="p">()))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">task</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">feedback</span> <span class="o">=</span> <span class="kr">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span><span class="nx">review</span><span class="p">(</span><span class="nx">result</span><span class="p">),</span> <span class="nx">review</span><span class="p">(</span><span class="nx">result</span><span class="p">)]);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">apply</span><span class="p">(</span><span class="nx">feedback</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></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Then compiler errors became a queue:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">~16,000 compiler errors left
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">error: deref *mut EventLoop before field access
</span></span><span class="line"><span class="cl">error[E0034]: multiple applicable items in scope
</span></span><span class="line"><span class="cl">error: event_loop/EventLoopTimer.rs: port Timespec::ns from bun.zig</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And the per-crate cycle was explicit:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">- For each crate, run cargo check, group the output by file and save the errors to a file
</span></span><span class="line"><span class="cl">- Fix all the compiler errors within that crate
</span></span><span class="line"><span class="cl">- 2 adversarial reviewers for the crate&#39;s changes
</span></span><span class="line"><span class="cl">- 1 fixer applies the fixes</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Source: <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">Bun&rsquo;s official post</a>.</p>
<p>He did not tell Claude to rewrite an entire runtime and go to sleep. He built loops. Separated implementer from reviewer. Used different context for adversarial review. Did a trial run. Fixed the workflow when agents started tripping over each other. Banned dangerous commands. Split worktrees. Turned compiler errors into a queue. Ran the same TypeScript suite because the suite did not depend on the runtime language. Used multi-platform CI as judge.</p>
<p>That is the part everyone should study.</p>
<p>The model did not replace engineering. The model executed work inside an engineering process. When the process was bad, the result was bad. When the process improved, the result improved.</p>
<p>It is the detail the tribal discussion ignores. The story is not &ldquo;Claude rewrote Bun&rdquo;. The story is &ldquo;an engineer turned a giant migration into several verifiable loops and used Claude to accelerate each loop&rdquo;.</p>
<p>Much more interesting.</p>
<h2>So did Zig fail?<span class="hx:absolute hx:-mt-20" id="so-did-zig-fail"></span>
    <a href="#so-did-zig-fail" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No.</p>
<p>Zig did not fail because Bun chose Rust. Rust also did not become the universal answer because Bun chose Rust.</p>
<p>What the public evidence shows is more boring: that code, at that moment, was too expensive to evolve with the level of confidence the team wanted. Rust bought predictability. Bought compiler feedback. Bought ecosystem. Bought an explicit safe/unsafe boundary. Bought tooling. Bought an environment where AI agents can bang against compiler errors and move forward mechanically.</p>
<p>That is enough to justify the decision.</p>
<p>But it is not enough to prove Zig was technically unviable for Bun. Many cited problems also look like boundary problems: implicit ownership, overly permissive APIs, allocation without a visible contract, resources crossing layers without a type carrying the owner, tests that maybe did not capture difficult invariants, and the human cost of reviewing all of that.</p>
<p>Those things could have been attacked in Zig. Maybe an aggressive Zig restructuring would have solved a lot. Maybe not. Maybe it would cost more than the port. Maybe it would cost less, but deliver less confidence. We do not have the counterfactual.</p>
<p>And that is exactly why the conclusion needs to be calibrated.</p>
<p>I support Jarred&rsquo;s decision as a product and engineering decision. His post is one of the most useful reports I have ever seen about a large AI-assisted rewrite. But liking the decision does not force me to pretend it was the only possible technical conclusion.</p>
<p>And there is one important disclaimer. <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">Anthropic acquired Bun in December 2025</a>, and Jarred opens the post by saying that he and the Bun team now work there. Anthropic sells Claude Code. Jarred had every institutional incentive to use as much Claude as possible and turn this rewrite into a public demonstration of his new employer&rsquo;s product. That does not prove bad faith, nor does it invalidate a technical result. But it adds bias to the decision and to the way the case is presented. Ignoring that conflict of incentives would be as naive as ignoring Andrew&rsquo;s resentment over Bun leaving Zig.</p>
<p>The best reading is less dramatic: Bun chose to pay a large cost now to reduce recurring maintenance costs later. Rust was a defensible choice, maybe a great one for that team at that moment. Zig remained an excellent language, but would have required another kind of reengineering: more explicit invariants, less informal ownership, more tools enabled by default, harder boundaries.</p>
<p>Andrew could have written that. He could have defended Zig on technical ground. He could have said: &ldquo;Rust bought some real guarantees, but a large part of the problem was architecture and process; here is how Zig could have attacked the same causes.&rdquo; That would have been a useful post.</p>
<p>We do not need to declare a winner. Language does not replace architecture. It only changes which errors become cheap to detect and which still depend on human discipline.</p>
<p>Bun chose to trade part of that discipline for compiler. I think it is a good bet.</p>
<p>I do not think it buries Zig.</p>
]]></content:encoded><category>programming-languages</category><category>rust</category><category>zig</category></item><item><title>Teaching the Latest Zig to Your LLM - Training LoRAs (Sort Of)</title><link>https://akitaonrails.github.io/en/2025/05/03/teaching-recent-zig-to-your-llm-training-loras-sort-of/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/05/03/teaching-recent-zig-to-your-llm-training-loras-sort-of/</guid><pubDate>Sat, 03 May 2025 02:30:00 -0300</pubDate><description>&lt;p&gt;&lt;a href="https://www.akitaonrails.com/en/2025/05/01/quando-llms-nao-funcionam-pra-programar-um-caso-de-uso-mais-real"target="_blank" rel="noopener"&gt;Two posts ago&lt;/a&gt; I demonstrated the Achilles&amp;rsquo; heel of LLMs for programming: they weren&amp;rsquo;t trained on the most recent data available. So if you ask about changes that came out last month in your favorite library, language or framework, they won&amp;rsquo;t know.&lt;/p&gt;
&lt;p&gt;Note: all the code I&amp;rsquo;m going to show below is available &lt;a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener"&gt;in this repository on my GitHub&lt;/a&gt;. Go poke around after reading to the end.&lt;/p&gt;
&lt;p&gt;For example, I asked the Qwen3-8b model (which came out a few days ago) which Zig version it knows about, and the answer was: 0.11 or 0.12:&lt;/p&gt;</description><content:encoded><![CDATA[<p><a href="https://www.akitaonrails.com/en/2025/05/01/quando-llms-nao-funcionam-pra-programar-um-caso-de-uso-mais-real"target="_blank" rel="noopener">Two posts ago</a> I demonstrated the Achilles&rsquo; heel of LLMs for programming: they weren&rsquo;t trained on the most recent data available. So if you ask about changes that came out last month in your favorite library, language or framework, they won&rsquo;t know.</p>
<p>Note: all the code I&rsquo;m going to show below is available <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">in this repository on my GitHub</a>. Go poke around after reading to the end.</p>
<p>For example, I asked the Qwen3-8b model (which came out a few days ago) which Zig version it knows about, and the answer was: 0.11 or 0.12:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/39h50oguk03tci9reclcbiovolhm" alt="zig antigo"  loading="lazy" /></p>
<p>By the way, the screenshot also shows how you call an OpenAI-compatible LLM API using curl. Anyway, that&rsquo;s why I suffered so much yesterday trying to get a simple &ldquo;Hello World&rdquo; level program working: it doesn&rsquo;t know we&rsquo;re already on <a href="https://ziglang.org/download/0.14.0/release-notes.html"target="_blank" rel="noopener">Zig 0.14</a>. And I did it on purpose, I knew it wouldn&rsquo;t have that information.</p>
<p><em>&ldquo;And other commercial LLMs? Aren&rsquo;t they better than the open source ones?&rdquo;</em></p>
<p>I asked ChatGPT 4.1, Claude Sonnet 3.7, Gemini 2.5 Pro Experimental, Deepseek R1 Zero (all at once, OpenRouter.ai allows multi-model chat):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/z9wbo5yi49m4vd3zi3pjpxbo7zj7" alt="LLM cut off"  loading="lazy" /></p>
<p>Summary:</p>
<ul>
<li>ChatGPT 4.1 says it only has data up to June 2024 and only knows up to Zig 0.11.</li>
<li>Claude Sonnet says it only has data up to April 2023 and only knows up to 0.10.</li>
<li>Gemini 2.5 Pro Exp says it only has data up to the start of 2023 and only knows up to Zig 0.11 as well.</li>
<li>Deepseek R1 (after some deep thinking&hellip;) answered short and blunt that it only has data up to October 2023 and only knows up to Zig 0.11.</li>
</ul>
<p><em>&ldquo;Why don&rsquo;t they have newer data?&rdquo;</em></p>
<p>I&rsquo;ll answer that by showing the reasons in the next sections.</p>
<p><em>&ldquo;But it can pull info from the internet, so it doesn&rsquo;t matter&rdquo;</em></p>
<p>It matters. That information is not IN the model. When you ask it to search the internet, the model doesn&rsquo;t know how to do that. It was instructed (in the fine-tuning/alignment phase or via prompts) to ask the chat system - the parser - to take that request, navigate the web (think curl, playwright, or anything like that), pull the site&rsquo;s content, copy and paste it only in your chat session (it&rsquo;s like a RAG solution). It only has that information in that session and other chats are not influenced by it.</p>
<p><em>&ldquo;But how does it show the source links when it replies?&rdquo;</em></p>
<p>It - probably - doesn&rsquo;t have that inside the model. What I imagine is simpler. They have catalogued and indexed all the original training material (including the source URLs) in a database (think Elastic, Postgres with full-text-search, or any vector database). When it finishes replying in the chat - the chat program and not the model -, it searches that snippet in the database for something similar enough and pulls the source URLs from there to show you.</p>
<p>Anyway.</p>
<p>Zig is experimental, pre-1.0. The syntax for everything changes A LOT from one version to the next, because they&rsquo;re still experimenting with what&rsquo;s best. And it&rsquo;s going to change more. In other words: it&rsquo;s impossible to do &ldquo;Vibe Coding&rdquo; in Zig unless you pin your compiler to version 0.11 or 0.12. But that would be &ldquo;lame&rdquo;, to say the least. I chose to try Zig on purpose, precisely to prove this point.</p>
<p>The same applies to a lot of companies with private code. Think of ERP companies that have their own languages, their own libraries, and none of it is public, so it was guaranteed not to be in OpenAI&rsquo;s training and it&rsquo;s certainly way more information than fits in a prompt context.</p>
<p><strong>&ldquo;Ah, just do a RAG&rdquo;</strong></p>
<p>Canned solution without understanding. No, it doesn&rsquo;t help. It will prioritize the training it already had. Second, for each thing you ask, you&rsquo;d need to know in advance that you&rsquo;ll need something that changed in 0.14, search that bit of documentation and keep adding it to the chat context. If you&rsquo;re going to do that method by method, why are you using the chat? Just go and write the code directly, it&rsquo;ll be faster.</p>
<p>Throwing ALL the documentation into the context doesn&rsquo;t work. I did the math: I downloaded the entire documentation and release notes, it&rsquo;s 200 thousand characters, roughly 50 thousand tokens. Most open source LLMs will blow past the window (average 32k tokens). Via commercial API/Chat you would pay per token, for nothing. Imagine wanting to refactor your 100-line piece of code and having to upload 200 thousand lines of context. Never do that. The cost-benefit doesn&rsquo;t justify it.</p>
<p><strong>&ldquo;Ah, but the Geminis of the world support 1 million tokens&rdquo;</strong></p>
<p>Yeah, with a sliding window, I&rsquo;ve repeated this several times. It can&rsquo;t pay attention to everything at once. Again: you&rsquo;ll <strong>PAY</strong> for those 50 thousand tokens, right? And it won&rsquo;t be cheap. Imagine, every new session having to upload 50 thousand tokens with no guarantee they&rsquo;ll work.</p>
<p>So is it screwed? Is there no way to use Zig with LLMs?</p>
<h3>RAGs?<span class="hx:absolute hx:-mt-20" id="rags"></span>
    <a href="#rags" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The RAG (Retrieval Augmented Generation, which is just a fancy name) hype makes it seem like it&rsquo;s something &ldquo;magical&rdquo;. But really, there&rsquo;s nothing special about it. It&rsquo;s good for when you have a large database that grows all the time, but in each chat session you only need a little bit of that information. Think of a run-of-the-mill e-commerce support team with support tickets. It grows every day, but for each call you only need the specific ticket for the person being helped, which is pretty short.</p>
<p>In the most rudimentary way, the attendant searches for the ticket in the good old Zendesk, copies the text, pastes it into the ChatGPT chat window and starts asking questions about that ticket, like &ldquo;can you summarize what this customer&rsquo;s problem is?&rdquo;, &ldquo;can you imagine a solution for them?&rdquo; and so on.</p>
<p>A RAG is a DATABASE with all those tickets pre-indexed to make relevance searches easier, and then it inserts the found snippet directly into the context window. It serves to automate, to SKIP the steps of manually searching in another system and doing copy and paste, but the effect is EXACTLY the same.</p>
<p><strong>&ldquo;Ah, but it uses VECTOR databases, that must be way cooler&rdquo;</strong></p>
<p>What&rsquo;s cool is hearing nonsense. It could be a Postgres and you&rsquo;d do a SELECT to find the ticket, it&rsquo;s exactly the same thing. If you enable &ldquo;full-text-search&rdquo; in Postgres, it will index your tickets in a &ldquo;VECTOR SPACE MODEL&rdquo;, which is essentially a VECTOR DATABASE.</p>
<p>If instead you prefer SOLR or ELASTIC, both are VECTOR DATABASES specialized in text search.</p>
<p><em>Duh, the more you know.</em></p>
<p>The main things:</p>
<ul>
<li>
<p>the more you stuff into the context (whether prompt text or &ldquo;embedding&rdquo; of a vector database with RAG - it&rsquo;s the same thing), the more you risk confusing the LLM and having it start hallucinating. The best prompts are structured, organized, with direct orders, and that explain the context clearly.</p>
</li>
<li>
<p>whether it&rsquo;s text-prompt or &ldquo;embedding&rdquo;, it will CONSUME input TOKENS. And if the window is small (open source models), it will blow up if you don&rsquo;t control the size of what you send as input. There&rsquo;s no magic.</p>
</li>
</ul>
<p>So what&rsquo;s the way out? Despite RAG being all people talk about, there&rsquo;s more than just RAG in the LLM world.</p>
<h3>Possible Solution: LoRAs<span class="hx:absolute hx:-mt-20" id="possible-solution-loras"></span>
    <a href="#possible-solution-loras" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>If instead of things that grow daily, the information is more &ldquo;permanent&rdquo; like instruction manuals, books, maybe RAGs aren&rsquo;t the best approach. Because it will always be TOO BIG and you&rsquo;d need to REPEAT THE SAME THING in every chat session. Especially rules that depend on other rules.</p>
<p>Yes, good providers have <a href="https://x.com/pashmerepat/status/1918084120514900395"target="_blank" rel="noopener">CACHE STATS</a> and that helps a lot. Even so it&rsquo;s no guarantee. Out of nowhere you&rsquo;re going to check your bill and be surprised by USD 500 suddenly spent on token credits.</p>
<p>Example: programming language documentation. Like my case with Zig 0.14.0. I&rsquo;d like the LLM to have that context, but uploading 50 thousand tokens every time is a lot if I only want to fix a 50-line file. Nobody thinks about these things or calculates context size vs chat/api interaction size, and they should: every wasted token is credit spent from your account.</p>
<p>There&rsquo;s a way to take an existing LLM model and add the missing training on top, without using context space. It would be as if the model were &ldquo;updated&rdquo; to know Zig 0.14. These are LoRAs (<strong>Low-Rank Adapter</strong>). It&rsquo;s a smaller kind of training we can add. We train small additional matrices (adapters) that attach to the original LLM&rsquo;s layers. When loaded, this updates the model&rsquo;s outputs without having to redo the entire training from scratch (which is the most expensive part and takes months in OpenAI&rsquo;s heavy datacenters, for example).</p>
<p>And here&rsquo;s a <strong>DISCLAIMER</strong>:</p>
<pre><code>I haven't studied enough yet about all the training parameters. Training is precisely the hardest phase. In particular: generating QUALITY training material and &quot;tuning&quot; the training parameters. There are people specialized in just this and there's no single recipe that works for everything. It's almost an &quot;art&quot; and takes much more dedication than I put in just for this post.
</code></pre>
<p>Some articles to start studying:</p>
<ul>
<li><a href="https://medium.com/@ufuk.birbiri/instruction-tuning-large-language-models-llms-with-huggingface-lora-and-sfttrainer-544bad770b50"target="_blank" rel="noopener">Instruction-Tuning Large Language Models (LLMs) with HuggingFace, LoRA and SFTTrainer</a></li>
<li><a href="https://huggingface.co/docs/trl/main/en/sft_trainer"target="_blank" rel="noopener"># Supervised Fine-tuning Trainer</a></li>
<li><a href="https://rocm.docs.amd.com/projects/ai-developer-hub/en/latest/notebooks/fine_tune/fine_tuning_lora_qwen2vl.html"target="_blank" rel="noopener"># Fine-tuning with the Hugging Face ecosystem (TRL)</a></li>
<li><a href="https://clay-atlas.com/us/blog/2024/10/08/en-multi-modal-large-language-model-sft-trainer/"target="_blank" rel="noopener"># Notes on Fine-Tuning a Multi-Modal Large Language Model Using SFTTrainer</a></li>
<li><a href="https://zohaib.me/a-beginners-guide-to-fine-tuning-llm-using-lora/"target="_blank" rel="noopener"># A beginners guide to fine tuning LLM using LoRA</a></li>
</ul>
<p>And there&rsquo;s a lot more. This is where you have to really understand the &ldquo;internals&rdquo;, the neural networks, loss function, gradients, etc. Don&rsquo;t underestimate training. It won&rsquo;t work on the first try, nor on the tenth. It&rsquo;s A LOT of trial and error and adjusting.</p>
<p>The final result is not exactly the same as a full training, but these adapters modify the parameters of the existing model&rsquo;s layers, changing the final result for specific things we want (that it knows Zig 0.14). It&rsquo;s MUCH cheaper than training from scratch. It doesn&rsquo;t guarantee it will work 100%, but it gives us an option to test, which can be more robust than a RAG.</p>
<p>The process is a miniature version of training a real model. We have to solve the following steps:</p>
<ul>
<li>get the training material (e.g. documentation)</li>
<li>write fine-tuning prompts (I need to &ldquo;teach&rdquo; how it should respond - this is the hardest part)</li>
<li>tokenize this information (trivial), load the base model (easy), and start training (the better the GPU, the less slow it is)</li>
<li>if it succeeds, you need to load this LoRA along with the base model</li>
<li>point tools like Aider at this new modified model.</li>
</ul>
<p>Let&rsquo;s go, step by step.</p>
<h3>1. Training Material<span class="hx:absolute hx:-mt-20" id="1-training-material"></span>
    <a href="#1-training-material" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I think this is the hardest part: getting quality training material to train with.</p>
<p>In the alignment case, this is done by building it by hand (with help from scripts, with help from another LLM to help generate synthetic texts), but ideally I&rsquo;d try to build a big JSON file with &ldquo;prompt&rdquo; and &ldquo;completion&rdquo; keys, something like:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{ &#34;prompt&#34;: &#34;How do the new @splat works for arrays in Zig 0.14.0?, &#34;completion&#34;: &#34;Zig 0.14.0 expands the `@splat` builtin to apply not only to vectors, but to arrays...&#34;}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, doing this for EACH NEW feature in version 0.14.0, including code examples, multiple times for each topic, imagining different ways to ask. And no, I didn&rsquo;t do it and I don&rsquo;t plan to, it&rsquo;s already much more work than I want to try in just this experiment. And I&rsquo;m not even sure if that&rsquo;s the best way yet. As I said before, it&rsquo;s practically an &ldquo;art&rdquo;.</p>
<p>The <a href="https://ziglang.org/download/0.14.0/release-notes.html"target="_blank" rel="noopener">RELEASE NOTES</a> file converted to Markdown has a ridiculous <strong>107 thousand characters</strong>. I&rsquo;d spend the rest of the week just re-editing it into prompt form. I&rsquo;m not going to do that. Instead, I&rsquo;ll just pass the entire raw text and hope that these new facts &ldquo;stick&rdquo; enough for the LLM to remember them. To pull it, it&rsquo;s simple:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>wget https://ziglang.org/download/0.14.0/release-notes.html -O zig-0.14.0-release-notes.html

# pip install html2text
html2text zig-0.14.0-release-notes.html &gt; raw_data/zig-0.14.0-release-notes.md</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Since I&rsquo;m on a roll, why not do the same thing with the Zig documentation? Fortunately the documentation is a single big HTML file. So there&rsquo;s no work to build a recursive web scraper or anything like that. Same procedure:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>wget https://github.com/ziglang/www.ziglang.org/raw/refs/heads/main/src/documentation/0.14.0/index.html -O raw_data/zig-0.14.0-docs.html

# pip install html2text
html2text raw_data/zig-0.14.0-docs.html &gt; raw_data/zig-0.14.0-docs.md</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That gave me a Markdown file of <strong>464 kilobytes</strong>. Since I think just that isn&rsquo;t enough, I thought it was a good idea to also grab the code examples in the <code>zig/doc</code> directory that I cloned directly from the <a href="https://github.com/ziglang/zig/tree/master/doc/langref"target="_blank" rel="noopener">official Zig repository</a>. I don&rsquo;t know what the best way is, but I tried to organize a single big file, with the name of the <code>.zig</code> file and the code delimited by a tag, with a prompt at the beginning indicating this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">zig_files</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;raw_data/zig/doc/langref/**/*.zig&#34;</span><span class="p">,</span> <span class="n">recursive</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">total_input_bytes</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="n">file_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="n">lines</span> <span class="o">=</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="n">filepath</span> <span class="ow">in</span> <span class="n">zig_files</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">file_bytes</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getsize</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_input_bytes</span> <span class="o">+=</span> <span class="n">file_bytes</span>
</span></span><span class="line"><span class="cl">    <span class="n">file_count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Reference Filename: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="se">\n\n</span><span class="s2">```zig</span><span class="se">\b</span><span class="si">{</span><span class="n">content</span><span class="si">}</span><span class="se">\b</span><span class="s2">```</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">outpath</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig_docs_with_filenames.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">outpath</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">lines</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">line</span> <span class="o">+</span> <span class="s2">&#34;</span><span class="se">\n\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Notice that in this case I&rsquo;m formatting each example starting with &ldquo;Reference filename: {filename}&rdquo; and then adding the code in a Markdown block with 3 backticks. Is it a good format? I don&rsquo;t know yet. Making training material IS HARD.</p>
<p>That resulted in another big file of <strong>152 kilobytes</strong>. I hope it doesn&rsquo;t confuse more than help. And I didn&rsquo;t stop there. I wanted to add the entire source code of the <a href="https://github.com/ziglang/zig/tree/master/lib/std"target="_blank" rel="noopener">Standard Library</a> and I even wrote the script, downloaded and compiled it into a structured format, but the final file was <strong>15 MEGABYTES</strong>. Unfortunately I had to comment it out of my training script. Now I hit a limitation of my RTX 4090: there&rsquo;s not enough VRAM to train all of that.</p>
<p>As I&rsquo;ve repeated before, the 4090 only has 24GB of VRAM. The Qwen3-8b model alone takes 16GB (no, the number of parameters influences the size but it doesn&rsquo;t mean 1 parameter is always 1 byte). There has to be memory left over to do all the training calculations, and it will spend 21GB while training. If I throw in that 15MB of material, this happens:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/lawgc11lm8bv08hc2u4ms62v4hm0" alt="out of memory vram"  loading="lazy" /></p>
<p>To train with all of that, it has to be remote, on a RunPod with an A40 like I showed in another blog post, at the very least. Or with faster GPUs like the H100, with 80GB, and more power to finish a little faster. So, I&rsquo;m already limited on quality training material and now limited on the amount of material I can use.</p>
<p>Since I couldn&rsquo;t use the source code (which would have real idiomatic 0.14.0 code examples), I looked for other examples to add and found this <a href="https://github.com/zigcc/zig-cookbook"target="_blank" rel="noopener">Zig Cookbook</a> repository, which is organized as problems in a Markdown file with solutions in 0.14-compatible code. I wrote this other script to organize everything into a single file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">lines</span> <span class="o">=</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="n">prob_file</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s1">&#39;raw_data/zig-cookbook/book-src/*.md&#39;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Extract the nn-nn from the problem filename (e.g., 01-05-matrix-mul.md -&gt; 01-05)</span>
</span></span><span class="line"><span class="cl">    <span class="n">base</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">prob_file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">base</span> <span class="o">==</span> <span class="s2">&#34;SUMMARY.md&#34;</span> <span class="ow">or</span> <span class="n">base</span> <span class="o">==</span> <span class="s2">&#34;database.md&#34;</span> <span class="ow">or</span> <span class="n">base</span> <span class="o">==</span> <span class="s2">&#34;intro.md&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">base</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">match_part</span> <span class="o">=</span> <span class="n">base</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;-&#39;</span> <span class="o">+</span> <span class="n">base</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>  <span class="c1"># e.g., 01-05</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">code_file</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;raw_data/zig-cookbook/src/</span><span class="si">{</span><span class="n">match_part</span><span class="si">}</span><span class="s2">.zig&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">code_file</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;WARN: No solution found for problem </span><span class="si">{</span><span class="n">prob_file</span><span class="si">}</span><span class="s2"> (expected </span><span class="si">{</span><span class="n">code_file</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">prob_file</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pf</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">problem_text</span> <span class="o">=</span> <span class="n">pf</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">code_file</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">cf</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">code_text</span> <span class="o">=</span> <span class="n">cf</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Compose chat format</span>
</span></span><span class="line"><span class="cl">    <span class="n">block</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="sa">f</span><span class="s2">&#34;&lt;|user|&gt;Solve this problem in Zig 0.14.0:</span><span class="se">\n</span><span class="si">{</span><span class="n">problem_text</span><span class="si">}</span><span class="se">\n</span><span class="s2">&lt;|endoftext|&gt;</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="sa">f</span><span class="s2">&#34;&lt;|assistant|&gt;This is the Zig 0.14.0 way to solve this:</span><span class="se">\n</span><span class="s2">```zig</span><span class="se">\n</span><span class="si">{</span><span class="n">code_text</span><span class="si">}</span><span class="se">\n</span><span class="s2">```</span><span class="se">\n</span><span class="s2">&lt;|endoftext|&gt;</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">block</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;raw_data/zig_cookbook_chat.txt&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">out</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">out</span><span class="o">.</span><span class="n">writelines</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Wrote </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span><span class="si">}</span><span class="s2"> problem/solution chat pairs to zig_cookbook_chat.txt&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In this case I&rsquo;m trying to see if using CHAT TAGS makes a difference. Not every model supports it. But Aider, for example, assembles prompts like this: with tags indicating &ldquo;who is speaking&rdquo; (system, user, assistant) and it seems to be an alternative to doing it in JSON format. So each problem/code looks like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;|user|&gt;### Measure the elapsed time between two code sections

[](https://github.com/zigcc/zig-cookbook/blob/main/book-src/03-01-elapsed-time.md#measure-the-elapsed-time-between-two-code-sections)
...
&lt;|endoftext|&gt;
&lt;|assistant|&gt;
...
fn expensive_function() void {
    // sleep 500ms
    time.sleep(time.ns_per_ms * 500);
}
&lt;|endoftext|&gt;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Will this work well? I don&rsquo;t know 😆 As I said, I need to spend many more hours dedicating myself just to understanding good training material formatting.</p>
<p>Finally, I tried to make a short file of prompts like I mentioned at the start, to try to force the chat to answer that it knows about the existence of version 0.14 and what the changes are in that version (just the topics from the release notes).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ir04nsralua23ada3wk45xvavm97" alt="training prompts"  loading="lazy" /></p>
<p>This is how the so-called <strong>&ldquo;alignment&rdquo;</strong> is done. At a big company like OpenAI, they must have HUNDREDS of human beings to write questions and answers that they want to force the LLM to reply with. And we have to do the same thing here, otherwise the Zig 0.11 and 0.12 training already in the base model might still override the new material I&rsquo;m adding.</p>
<p>I understand there&rsquo;s no trivial way to say <em>&ldquo;ignore the old version and only pull from my new material&rdquo;</em>. That&rsquo;s not how it works. You &ldquo;influence&rdquo; the training (<strong>supervised training</strong>), but there are no &ldquo;guarantees&rdquo; that every variation of a question will give the same answer, unless you manually input most of the variations in the alignment.</p>
<pre><code>This is why I say DO NOT BELIEVE an LLM. It is not REASONING. It was FORCED to reply in a certain way by humans, during training. It literally has millions of PRE-PROGRAMMED ANSWERS already embedded in training. That's how you CONTROL the answers.
</code></pre>
<p>In my case, it&rsquo;s not enough to just upload the documentation, I should say in the prompts &ldquo;what are the language changes in Zig 0.14.0?&rdquo; and give the answer &ldquo;In Zig 0.14.0 we had the following language changes: Labeled Switch, Code Generation Properties, Decl Literals, Fields and Declarations Cannot Share Names, @splat Supports Arrays, Global Variables can be Initialized with Address of Each Other, @export Operand is Now a Pointer, New @branchHint Builtin, Replacing @setCold, &hellip;&hellip;.&rdquo; (yes, a lot of stuff really did change).</p>
<p>Anyway. I&rsquo;ll push my training procedure <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">to my GitHub</a>. There you&rsquo;ll find a <code>raw_data</code> directory where the material I compiled lives. There are scripts like <code>concat-zig-docs.py</code>, <code>concat-zig-stdlib.py</code> or <code>concat-zig-cookbook.py</code> to concatenate each group of material into a single big file as I explained. And at the end, there&rsquo;s <code>train-zig.py</code> which actually loads all the datasets and does the training.</p>
<p>There&rsquo;s another important factor: compared to the more than 800 kilobytes of material I compiled, the prompts I mentioned are still very small, only 8.3 kilobytes. We need to try to do <strong>OVER-SAMPLING</strong>. Ideally I should write at least 10x more real prompts manually, but I really don&rsquo;t have that much patience. What I&rsquo;m going to do is repeat these prompts several times:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">json_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/training-prompts.json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig-0.14.0-docs.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text2_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig-0.14.0-release-notes.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text3_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig_docs_with_filenames.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text4_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig_cookbook_chat.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text5_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig_stdlib_concat.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Prepare HuggingFace dataset for text</span>
</span></span><span class="line"><span class="cl"><span class="n">text5_dataset</span> <span class="o">=</span> <span class="n">load_dataset</span><span class="p">(</span><span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="n">data_files</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;train&#34;</span><span class="p">:</span> <span class="n">text5_dataset_path</span><span class="p">})[</span><span class="s1">&#39;train&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">json_dataset</span> <span class="o">=</span> <span class="n">load_dataset</span><span class="p">(</span><span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="n">data_files</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;train&#34;</span><span class="p">:</span> <span class="n">json_dataset_path</span><span class="p">})[</span><span class="s1">&#39;train&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># oversample the prompts to try to influence the bias towards this new documentation</span>
</span></span><span class="line"><span class="cl"><span class="n">N</span> <span class="o">=</span> <span class="mi">100</span>  <span class="c1"># Oversampled target size</span>
</span></span><span class="line"><span class="cl"><span class="n">indices</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">json_dataset</span><span class="p">),</span> <span class="n">size</span><span class="o">=</span><span class="n">N</span><span class="p">,</span> <span class="n">replace</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oversampled_ds</span> <span class="o">=</span> <span class="n">json_dataset</span><span class="o">.</span><span class="n">select</span><span class="p">(</span><span class="n">indices</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># text5 (std) won&#39;t fit in the 4090 so removed from combined</span>
</span></span><span class="line"><span class="cl"><span class="n">combined_dataset</span> <span class="o">=</span> <span class="n">concatenate_datasets</span><span class="p">([</span><span class="n">text_dataset</span><span class="p">,</span> <span class="n">text2_dataset</span><span class="p">,</span> <span class="n">text3_dataset</span><span class="p">,</span> <span class="n">text4_dataset</span><span class="p">,</span> <span class="n">oversampled_ds</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That&rsquo;s how you load the training data as <code>datasets</code>. And I use Numpy&rsquo;s <code>random_choice</code> to over-sample the JSON file and artificially make it bigger, repeating the lines randomly. And in the end we have a <code>combined_dataset</code> that is everything concatenated, about almost 900 kilobytes of material.</p>
<p>Then read the <code>train_lora.py</code> file, but with the combined dataset ready, now it&rsquo;s the actual training part:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Tokenize your dataset FIRST:</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">tokenize_function</span><span class="p">(</span><span class="n">example</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">tokenizer</span><span class="p">(</span><span class="n">example</span><span class="p">[</span><span class="s2">&#34;text&#34;</span><span class="p">],</span> <span class="n">truncation</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">padding</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">tokenized_dataset</span> <span class="o">=</span> <span class="n">combined_dataset</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">tokenize_function</span><span class="p">,</span> <span class="n">batched</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Training hyperparameters</span>
</span></span><span class="line"><span class="cl"><span class="n">training_args</span> <span class="o">=</span> <span class="n">SFTConfig</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">output_dir</span> <span class="o">=</span> <span class="s2">&#34;qwen3-zig-lora&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">per_device_train_batch_size</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">gradient_accumulation_steps</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">warmup_steps</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">num_train_epochs</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">learning_rate</span> <span class="o">=</span> <span class="mf">2e-4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">fp16</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">is_bfloat16_supported</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="n">bf16</span> <span class="o">=</span> <span class="n">is_bfloat16_supported</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="n">optim</span> <span class="o">=</span> <span class="s2">&#34;adamw_8bit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">weight_decay</span> <span class="o">=</span> <span class="mf">0.01</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">lr_scheduler_type</span> <span class="o">=</span> <span class="s2">&#34;linear&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">seed</span> <span class="o">=</span> <span class="mi">3407</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">report_to</span> <span class="o">=</span> <span class="s2">&#34;none&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">max_seq_length</span> <span class="o">=</span> <span class="mi">2048</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">dataset_num_proc</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">packing</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="c1"># Can make training 5x faster for short sequences.</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"># Trainer for unsupervised SFT</span>
</span></span><span class="line"><span class="cl"><span class="n">trainer</span> <span class="o">=</span> <span class="n">SFTTrainer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="n">model</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">args</span><span class="o">=</span><span class="n">training_args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">train_dataset</span><span class="o">=</span><span class="n">tokenized_dataset</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">processing_class</span><span class="o">=</span><span class="n">tokenizer</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">trainer</span><span class="o">.</span><span class="n">train</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">model</span><span class="o">.</span><span class="n">save_pretrained</span><span class="p">(</span><span class="s2">&#34;qwen3-zig-lora&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">tokenizer</span><span class="o">.</span><span class="n">save_pretrained</span><span class="p">(</span><span class="s2">&#34;qwen3-zig-lora&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>First, we tokenize the dataset, we convert text into tokens (this is fast; in the chat you use every day, your text is converted into tokens like this too - the model doesn&rsquo;t understand text, it understands embeddings, which are these tokens translated into vectors).</p>
<p>In <code>SFTConfig</code> we configure the training parameters. I think most of them are default values, but there are two important ones:</p>
<ul>
<li>
<p>Epoch: One pass through the training material is called &ldquo;one Epoch&rdquo;. They say for little material (like my case), it&rsquo;s good to go more than 3 Epochs, up to 10 Epochs. More Epochs tries to make my new LoRA &ldquo;stick&rdquo; more. If the dataset were orders of magnitude larger, fewer Epochs would be better, because there&rsquo;s the problem of <strong>OVER-FITTING</strong>. It &ldquo;sticks&rdquo; too much and the model will memorize my material too much and just copy-paste directly. It depends on the type of material, depends on the amount of material. With 5 Epochs this material takes more than 1 hour to process, 10 Epochs then pushes it to more than 2 hours.</p>
</li>
<li>
<p>Learning Rate: it&rsquo;s the scale factor of how much we want the &ldquo;weights&rdquo; inside the model to change. How aggressively do we want this new information to change the model weights, so it actually &ldquo;learns&rdquo;? If it&rsquo;s too high, it&rsquo;ll start to &ldquo;forget&rdquo; what it already had in the prior training and create knowledge gaps (&ldquo;catastrophic forgetting&rdquo;). If it&rsquo;s too low, it changes little, and our LoRA won&rsquo;t have any significant effect.</p>
</li>
</ul>
<p>There&rsquo;s one more parameter that leaves me in doubt, <code>max_seq_length</code>, which serves to truncate overly long text. Theoretically the <code>unsloth</code> library knows how to split training material into smaller &ldquo;chunks&rdquo; (if it has obvious delimiters). But I don&rsquo;t know what happens with giant things, like the entire documentation. Whether it splits and uses it, or truncates and doesn&rsquo;t use it. There are many details that still need more studying. If it&rsquo;s truncating without me knowing, maybe I&rsquo;m feeding material it&rsquo;s not actually using, for example.</p>
<p>The old facts that already exist in training (Zig 0.11 and 0.12) are stronger than my material. So cranking up those parameters might help influence the final result more. That&rsquo;s the other part of the &ldquo;art&rdquo;: keeping adjusting these values to better fit the new knowledge. In a full training, from scratch, it&rsquo;s even more important. I don&rsquo;t know everything about this. What I said is the amateur level of training an LLM.</p>
<p>A LoRA is supposed to be small. With an 8B base model, with this training material size, the final LoRA will be in the 3 to 4GB range (I don&rsquo;t know if that&rsquo;s considered big or small). And that has to be enough to redirect the responses to the new knowledge. If you didn&rsquo;t know this, on sites like Hugging Face you&rsquo;ll find pre-trained LoRAs to use, you don&rsquo;t necessarily have to make one from scratch if there&rsquo;s already a ready one.</p>
<p>With the material in place, processed, organized, just run my <code>train_lora.py</code> and you&rsquo;ll see something like:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/s3rpq8b6a3h1p0qt60yazf042cr7" alt="training"  loading="lazy" /></p>
<p>And with so many Epochs and high Learning Rate, it will take a while! On my RTX 4090 it should take <strong>more than 1 hour training</strong> (with 5 Epochs), maybe more (I didn&rsquo;t time it), and while that&rsquo;s running it really will nail the GPU:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ivjodz630t9svrpvofuzpmgjanhe" alt="GPU usage"  loading="lazy" /></p>
<p>I know there are online services to train remote LoRAs, I think Hugging Face itself has APIs for it, or we can spin up a more powerful GPU on a RunPod. Obviously, you need access to the model to train on top of it (the LoRA parameters are dependent on the base model) so there&rsquo;s no way to make a LoRA of GPT 4.1 on your machine. That depends on whether OpenAI has APIs to train LoRAs remotely (I think they do, I didn&rsquo;t look).</p>
<p>Repeating: LoRAs are specific to the base model. Say you made a LoRA for the Qwen3-8b model (my case). Will that LoRA work for Qwen3-32b? No. It&rsquo;s specific. You have to load exactly the same base model when using your LoRA.</p>
<p>It&rsquo;s up to you, you don&rsquo;t need to have a local GPU just for this. It&rsquo;s an intensive process, that uses a lot of resources and a lot of energy and, depending on how much training material and how many epochs&hellip; go grab a coffee.</p>
<p>At the end, a <code>qwen3-zig-lora</code> directory will appear more or less like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/83h7fwsx0jsod3h17ruv4ag237r1" alt="lora"  loading="lazy" /></p>
<p>It records a checkpoint per epoch or something like that, from what I understand. Which means that depending on the strategy, if needed you can stop training and then continue from where you left off. I haven&rsquo;t tried to do this, but I imagine in a real training, which takes MONTHS, this is essential.</p>
<h3>Another Problem - Ollama&rsquo;s Limitations<span class="hx:absolute hx:-mt-20" id="another-problem---ollamas-limitations"></span>
    <a href="#another-problem---ollamas-limitations" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>To use a local model, as I already showed <a href="https://www.akitaonrails.com/en/2025/04/25/seu-proprio-co-pilot-gratuito-universal-que-funciona-local-aider-ollama-qwen"target="_blank" rel="noopener">in this other article</a>, we can load an LLM like Qwen3 with the Ollama server, which will spin up a web server and serve APIs in the OpenAI standard. Then just point a tool like Aider at it and done, we have a code assistant running locally.</p>
<p>The problem: LoRA support in Ollama is limited (it may be lack of research on my part, but I didn&rsquo;t find a quick solution). They use a proprietary format for the models, and not <code>.safetensors</code> files like those on Hugging Face. Anyway, I hit that problem and I think it&rsquo;s not easy to proceed with Ollama.</p>
<p>So we have to switch and, fortunately, there are options. The most popular alternative is <a href="https://blog.vllm.ai/2023/06/20/vllm.html"target="_blank" rel="noopener">vLLM</a>. It does the same thing: loads models and spins up an HTTP API server in the OpenAI standard. The advantage is it&rsquo;s more flexible (works with Hugging Face models), more advanced, more customizable and the main thing: it can load <a href="https://docs.vllm.ai/en/latest/features/lora.html"target="_blank" rel="noopener">models with LoRAs</a>. Check the documentation at the previous link, but in my case I think it&rsquo;s pretty simple.</p>
<p>And for those thinking about deploying for multiple users, vLLM is more performant and faster than Ollama too. Even for RAG solutions: Ollama only accepts prompt injection, while vLLM will integrate RAG better via OpenAI/proxy/Langchain for example.</p>
<p>You may need to study the options a bit more, but technically speaking, it&rsquo;s superior. I always think of Ollama more for running locally on your own machine, but not for production. vLLM I &ldquo;think&rdquo; would be better for that purpose - worth researching later if you&rsquo;re interested.</p>
<p>My training, in the end, will generate a directory called <code>qwen3-zig-lora</code> and with vLLM we can spin up the server like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># pip install vllm[all]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">vllm serve Qwen/Qwen3-8B <span class="se">\
</span></span></span><span class="line"><span class="cl">    --enable-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --lora-modules <span class="nv">ziglora</span><span class="o">=</span>./qwen3-zig-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --max_model_len <span class="m">8192</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>To test that it&rsquo;s up, we can run a simple curl command:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl localhost:8000/v1/models <span class="p">|</span> jq .</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And we&rsquo;ll have the lora listed as if it were just another model. See <code>ziglora</code> at the bottom:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;object&#34;</span><span class="p">:</span> <span class="s2">&#34;list&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</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="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;Qwen/Qwen3-8B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;object&#34;</span><span class="p">:</span> <span class="s2">&#34;model&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;created&#34;</span><span class="p">:</span> <span class="mi">1746207038</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;owned_by&#34;</span><span class="p">:</span> <span class="s2">&#34;vllm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;root&#34;</span><span class="p">:</span> <span class="s2">&#34;Qwen/Qwen3-8B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;parent&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;max_model_len&#34;</span><span class="p">:</span> <span class="mi">8192</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;permission&#34;</span><span class="p">:</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="err">...</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><span class="line"><span class="cl">      <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;ziglora&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;object&#34;</span><span class="p">:</span> <span class="s2">&#34;model&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;created&#34;</span><span class="p">:</span> <span class="mi">1746207038</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;owned_by&#34;</span><span class="p">:</span> <span class="s2">&#34;vllm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;root&#34;</span><span class="p">:</span> <span class="s2">&#34;./qwen3-zig-lora&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;parent&#34;</span><span class="p">:</span> <span class="s2">&#34;Qwen/Qwen3-8B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;max_model_len&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;permission&#34;</span><span class="p">:</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="err">...</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><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>To chat with it, we can directly use the API with curl and send the message to the LoRA:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/f1etshmornpoawgci7ndb7791r3a" alt="curl lora"  loading="lazy" /></p>
<p>And pay attention to the response. I asked which is the most recent version of Zig it&rsquo;s familiar with and the answer was <em>&ldquo;I am familiar with the Zig language up to version 0.14.0 from 2025.&rdquo;</em></p>
<p>I can&rsquo;t say if it&rsquo;s true 😂 this only means that the weight of my prompt over-sampling with canned answers is working. Maybe it didn&rsquo;t learn anything about Zig 0.14.0 but it learned to reply that it knows. And, again, this is why you shouldn&rsquo;t trust LLMs: having a canned answer forced in alignment doesn&rsquo;t mean it actually &ldquo;knows&rdquo; what it&rsquo;s talking about, I just forced it to answer with conviction that yes. And that part worked.</p>
<p>Qwen3-8b has a tiny context window, only 8K tokens. So in <code>.aider.model.settings.yaml</code> you have to configure it like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">hosted_vllm/ziglora</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">edit_format</span><span class="p">:</span><span class="w"> </span><span class="l">diff</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">use_repo_map</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extra_params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">max_tokens</span><span class="p">:</span><span class="w"> </span><span class="m">8192</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">temperature</span><span class="p">:</span><span class="w"> </span><span class="m">0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">accepts_settings</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">reasoning_effort</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>To run Aider, I&rsquo;m temporarily doing it this way:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">OPENAI_API_BASE</span><span class="o">=</span>http://localhost:8000/v1 aider --watch-files --model hosted_vllm/ziglora --reasoning-effort none --verbose</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Since it&rsquo;s an API compatible with OpenAI&rsquo;s, I just point to localhost in the environment variable. And since it&rsquo;s a vLLM model instead of &ldquo;openai/&hellip;&rdquo; you have to use &ldquo;hosted_vllm/&hellip;&rdquo;. Also to save tokens, I&rsquo;m turning off Aider&rsquo;s repo-maps and trying to turn off the model&rsquo;s thinking with <code>-reasoning-effort none</code> but I don&rsquo;t know if it works, I still see it doing some long thinking.</p>
<p>I would have preferred to use the Qwen3-14b model but as I said, it wouldn&rsquo;t fit on my 4090, with the 8B I&rsquo;m already at the limit, and I wasn&rsquo;t in the mood to spin one up on RunPod. Plus I had no idea how many times I&rsquo;d need to repeat the training (as I&rsquo;m writing this post, I&rsquo;ve already repeated it at least 4 times, each taking over 1 hour). At least running locally I know I&rsquo;m not being charged a lot for some online service. Later, if it actually works, I&rsquo;ll try with a bigger model, more material, on a more powerful machine with more VRAM to fit it.</p>
<h3>Tangent: Learning a Language<span class="hx:absolute hx:-mt-20" id="tangent-learning-a-language"></span>
    <a href="#tangent-learning-a-language" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I suspect my training material is still very weak. Let&rsquo;s think:</p>
<p>If you only read the documentation (let&rsquo;s say you memorize it), if you only read small examples like the Cookbook (let&rsquo;s say you memorize it), if you only read a release note. In the end can we say you&rsquo;re now a senior Zig dev? Hardly. For an LLM it&rsquo;s also not that simple. It needs MUCH more material.</p>
<p>An example would be reading the entire source code of the Standard Library, to really know how each structure, each function really works under the hood. And even that is little.</p>
<p>You&rsquo;d need to build a scraper to filter all of StackOverflow looking for Zig 0.14 problems and solutions. Pain points in converting code from 0.12 to 0.14. As much as possible. You learn like that: having a problem and searching for another person who had the same problem in the past, to see how they solved it.</p>
<p>You&rsquo;d need to fetch all the Issues in the repository. Bug reports or false positives of not understanding the documentation, for example. How they were resolved is the important part.</p>
<p>You&rsquo;d need to search for more than just a Cookbook. If there are books, courses, exercises, you&rsquo;d need to organize all of that in training.</p>
<p>More than that, you&rsquo;d need to know C/C++ the same way, you&rsquo;d need to know the GCC and Clang compilers. You&rsquo;d need to know all the C/C++ problems that Zig says it improves, in various code examples. Learning Zig at a senior level isn&rsquo;t just Zig, it&rsquo;s the whole rest of the ecosystem.</p>
<p>You&rsquo;d need to see how, for example, in Rust various problems are solved and have similar solutions in Zig, to have a comparison point, especially in hard problems involving performance optimization, memory management, concurrency management, security, etc.</p>
<p>You&rsquo;d need to go after the main open source projects that use new Zig, classify what problems they solve, read all the source code. All of this would need to be in the training material, just like a programmer would also have to do to go from amateur to advanced, from junior to senior.</p>
<p>I don&rsquo;t know what materials were used in a Qwen (or Claude, or Gemini) to train Zig. I hope it was at least this. Otherwise it really won&rsquo;t be able to write real Zig code.</p>
<p>Except that much material starts reaching the gigabytes-of-data category to train on. I wouldn&rsquo;t know anymore whether it still fits to keep using LoRAs or whether it already falls into the category of creating checkpoints on top of the model itself (everything in the same LLM &ldquo;big file&rdquo;). These are doubts I still don&rsquo;t have answers to.</p>
<h3>First Impressions of the LoRA<span class="hx:absolute hx:-mt-20" id="first-impressions-of-the-lora"></span>
    <a href="#first-impressions-of-the-lora" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Unfortunately, since I used Qwen3-8b as the base, I was also limited to its super short context window, only 8192 tokens. So I can&rsquo;t use Aider, because just the system prompt it uses to instruct the model already consumes almost everything and there&rsquo;s no room left for it to reply. So my tests were limited because I had to chat with the model directly through the API, using <code>curl</code> and <code>jq</code> to see the responses.</p>
<p>That&rsquo;s what the <code>--max-model-len</code> in the call to spin up vLLM is for, otherwise it tries to spin up a too-large default window (40k) and blows out my RTX 4090&rsquo;s VRAM:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/5sb1dj2x0st18wsbyecwef7lbwk6" alt="vllm out of memory"  loading="lazy" /></p>
<p>Notice how I&rsquo;m sending super short prompts: there&rsquo;s no additional context from RAG or anything like that to try to teach Zig 0.14. Anything it replies is permanently in the LoRA training now.</p>
<p>Repeating the first test:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl http://localhost:8000/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;model&#34;: &#34;ziglora&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Hello, which most recent version of the Zig language are you familiar with?&#34;}]                    }&#39;</span> <span class="p">|</span> jq</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Here&rsquo;s the response (just the message, I removed the JSON to not make it too long here):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&#34;&lt;think&gt;
I have knowledge of Zig up to version 0.14.0, released in 2025. My training data includes documentation for Zig version 0.14.0, so I understand its new features, such as the default C Library being now LazyLoaded, and the new `@hasDecl` builtin. However, I am not aware of any changes made to Zig after that release. If you have a question about Zig 0.14.0 or earlier, I&#39;ll be happy to help! If you have a question about Zig 0.15.0 or newer, I recommend checking the official documentation or community resources for the latest information.
&lt;/think&gt;

I have knowledge of Zig up to version 0.14.0, released in 2 GitHub Oct 2025. I know about its new features, such as the default C Library being now LazyLoaded, and the new `@hasDecl` builtin. However, I am not aware of any changes made to Zig after that release. If you have a question about Zig 0.14.0 or earlier, I&#39;ll be happy to help! If you have a question about Zig 0.15.0 or newer, I recommend checking the official documentation or community resources for the latest information.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>See how the &ldquo;deep thinking&rdquo; or &ldquo;reasoning&rdquo; part is returned. This model does this and it&rsquo;s bad because the context window is small and these &ldquo;think&rdquo; tokens take up space in the context. See how it managed to answer that it knows 0.14 and even some of the new stuff in it. Let&rsquo;s continue:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl http://localhost:8000/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;model&#34;: &#34;ziglora&#34;, &#34;temperature&#34;: &#34;0.1&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Can you quickly explain the new @splat support for Arrays in Zig 0.14.0? can you give me a code example&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">        }&#39;</span> <span class="p">|</span> jq .</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And here&rsquo;s the response:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;think&gt;
Okay, the user is asking about the new @splat functionality for Arrays in Zig 0.14.0. First, I need to recall what changes were introduced in that version regarding Arrays and @splat. From what I remember, prior to 0.14, Arrays couldn&#39;t be directly splatted into a tuple because the splat operator required a known, fixed number of elements. The new feature allows Arrays to be splatted into tuples, similar to how slices work with tuples.

So, the user wants an explanation and a code example. Let me think of a simple demonstration. Maybe creating an array and then using @splat to convert it into a tuple. I should also mention the syntax, like how the number of arguments in the tuple matches the array length. Oh, and note that this is a new feature in 0.14, so it&#39;s important to mention the version. Let me make sure the example is clear and concise. I&#39;ll show the array initialization, then the splatted tuple, and then use them in a function call to illustrate the point. That should cover the basics without overwhelming the user. Also, I should explain why this is useful—making the code more readable and avoiding the need for explicit tuple syntax. Alright, that should do it.
&lt;/think&gt;

Here&#39;s an example demonstrating `@splat` with Arrays in Zig 0.14.0:  

const std = @import(\&#34;std\&#34;);  

const Color = struct {  
    red: u8,  
    green: u8,  
    blue: u8,  
};  

const red = Color{ .red = 255, .green = 0, .blue = 0 };  
const green = Color{ .red = 0, .green = 255, .blue = 0 };  
const blue = Color{ .red = 0, .green = 0, .blue = 255 };  

const palette = .{ red, green, blue };  // Array of 3 elements  

const color_func = @splat(palette);  // Result type: (Color, Color, Color)  

pub fn main() !void {  
    // Use the splatted tuple!  
    _ = color_func;  
}  

### Key Points:  
- `@splat` infers the tuple element type from the array type.  
- The number of elements in the tuple matches the array length.  
- This syntax is available in Zig 0.14.0 and simplifies working with Arrays.  

This pattern is particularly useful when passing multiple related values to functions or initializing structs.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The response itself seems pretty okay. It didn&rsquo;t truncate, no Chinese characters showed up (that happened in one of my failed trainings). Let&rsquo;s compare it with the official release note on the site:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/fuks9uthx1l56sfpcltclhlx4n2h" alt="release note splat"  loading="lazy" /></p>
<p>To be honest, I can&rsquo;t say if it&rsquo;s correct (I don&rsquo;t know Zig yet either 🤣). But at a glance I think it made up waffle, it doesn&rsquo;t look right.</p>
<p>Now let&rsquo;s see the harder test: trying to write a small program.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> curl http://localhost:8000/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;model&#34;: &#34;ziglora&#34;, &#34;temperature&#34;: &#34;0.1&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Can you give me a Zig 0.14 code example of a simple program that acts like an interactive chat, so the user can input messages. then the message is parsed to see if there are commands such as &#39;</span>/list<span class="s1">&#39;, which will list the files in the current directory, or &#39;</span>/cat<span class="s1">&#39; which will read the file asked, or &#39;</span>/quit<span class="s1">&#39; to exit the chat?&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">      }&#39;</span> <span class="p">|</span> jq </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The response was VERY LONG (lucky it didn&rsquo;t actually blow the context). I&rsquo;ll leave it in full so you can see how the model with the lora loaded managed to respond:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;think&gt;
Okay, let&#39;s see. The user wants a Zig program that acts like an interactive chat, handling commands like /list, /cat, and /quit. They specified using Zig 0.14, so I need to make sure the code is compatible with that version. 

First, I&#39;ll outline the main components. The program needs to read user input line by line, parse each line for commands, and execute the corresponding actions. 

For handling input, I&#39;ll use std.process.argsAlloc to get command-line arguments, but wait, in this case, the program is meant to be interactive, so maybe that&#39;s not the right approach. Oh, right, the user wants to input messages interactively, not pass them as command-line arguments. So I should read from stdin line by line.

So, I&#39;ll start by reading lines using a loop. Each line will be checked for commands. The commands are /list, /cat, /quit. 

For /list, I need to list files in the current directory. Using std.fs.Dir.openDir on the current directory, then iterating over the entries. But I have to handle errors properly. 

For /cat, the user will input something like /cat filename, so I need to parse the filename. Then open the file, read its contents, and display them. Again, error handling is important here. 

/quit should exit the loop, ending the program. 

Now, structuring the code. I&#39;ll use a while loop that reads a line at a time. Each iteration will process the command. 

Wait, but how to read lines interactively? Using std.process.stdin, perhaps. So, I&#39;ll create a Reader for stdin, then read lines into a string. 

But in Zig, reading lines can be done with the readline function, but I need to handle EOF. However, in an interactive shell, the user can type Ctrl&#43;D (EOF on Unix) to exit, but maybe I should handle that gracefully. 

So, the main loop will look like this:

var line = std.ArrayList(u8).init(allocator); 
while (true) { 
    if (!readLine(allocator, &amp;line)) break; 
    processLine(line); 
} 

But how to implement readLine? Maybe using a function that reads until a newline is encountered, handling errors. 

Then, for each line, split the line into parts. For example, /list is a single command, but /cat needs the filename. So, splitting the line by spaces, then checking the first part. 

Wait, but /cat needs the filename as the second argument. So, for /cat, the line should be split into parts, and the second part is the filename. But what if the filename has spaces? Probably, the code should split on the first space, since the command is the first token. 

So, for each line:

var parts = line.split(\&#34; \&#34;); 
var command = parts[0]; 

Then, handle each case. 

Now, error handling. For example, when listing files, if the directory cannot be opened, we should print an error message. Similarly for file reading. 

Also, need to free the allocator when done. 

Putting it all together. Let me draft the code. 

First, the imports. I&#39;ll need std&#39;s process, fs, and so on. 

Then, the main function. 

But wait, in Zig, the main function can be written with a while loop reading lines. 

Wait, but how to read lines interactively? Using a Reader. 

So, here&#39;s a possible structure:

const std = @import(\&#34;std\&#34;); 

export fn main() !void { 
    const stdin = std.io.getStdIo(); 
    var reader = stdin.reader(); 

    var line = std.ArrayList(u8).init(allocator); 

    while (true) { 
        const input = try reader.readUntilAlloc(allocator, &#39;\
&#39;, 4096); 
        if (input.len == 0) break; 
        // process the line here 
        // ... 
        line.clear(); 
    } 

    // ... 
} 

Wait, but this code may not handle all cases. Also, after reading, we need to process the line. 

But perhaps using a loop with readline function. However, in Zig 0.14, the std.io.getStdIo().reader() can be used. 

Alternatively, using a loop with a std.io.BufferedReader. 

But maybe the simplest way is to read line by line using the reader.readUntilAlloc function. 

So, in the code, I&#39;ll read each line, then split into parts. 

Now, handling the commands. 

For /list: list the files in the current directory. 

For that, I can use std.fs.cwd() to get the current directory, then openDir. 

Then, iterate over the entries. 

But I need to handle errors. So, for example:

var dir = try current_dir.openDir(.{}); 
var iter = try dir.iterate(); 
while (try iter.next()) |entry| { 
    // process entry.name 
} 

But I have to handle all possible errors here. 

For /cat, I need to take the second part of the line. So, if the line is \&#34;/cat filename\&#34;, then the second part is the filename. But what if there are multiple spaces? Like \&#34;/cat  file name\&#34;? In that case, the code would split into parts, and the second part is \&#34;file\&#34;, but that&#39;s not correct. However, this code would not handle such cases correctly. But for simplicity, maybe we can assume that the filename is the rest of the line after the command. 

Alternatively, we can take the entire line after the command. For example, for /cat, the arguments are the rest of the line. 

But splitting into parts may not be the best approach. Maybe using the first token to determine the command, and then the rest of the line as arguments. 

So, for example:

if command == \&#34;/list\&#34; { 
    // list files 
} else if command == \&#34;/cat\&#34; { 
    if (parts.len &lt; 2) { 
        print(\&#34;Usage: /cat &lt;filename&gt;\
\&#34;, .{}); 
        continue; 
    } 
    const filename = parts[1]; 
    // read file 
} else if command == \&#34;/quit\&#34; { 
    return; 
} 

But again, this approach may not handle filenames with spaces correctly. However, for the scope of this example, it&#39;s acceptable. 

Now, putting all together. 

Also, need to handle the case where the line is empty, so we can just continue. 

Now, the code. 

But first, the error handling. For example, when opening a directory, if it fails, we should print an error message. 

Similarly, when reading a file, if it fails, print an error message. 

Now, the code. 

But wait, in Zig, the main function returns a !void, so we need to handle any errors. 

Putting it all together, here&#39;s the code example. 

I&#39;ll also include the necessary error handling. 

Testing for /list, /cat, /quit commands. 

Now, I think this should work. Let me write the code.
&lt;/think&gt;

Here&#39;s a Zig 0.14 compliant example of an interactive chat program that handles `/list`, `/cat`, and `/quit` commands:  


const std = @import(\&#34;std\&#34;);  

export fn main() !void {  
    const stdin = std.io.getStdIo();  
    const reader = stdin.reader();  

    var line = std.ArrayList(u8).init(allocator);  

    while (true) {  
        const input = try reader.readUntilAlloc(allocator, &#39;\
&#39;, 4096);  
        if (input.len == 0) break;  
        line.appendSlice(input);  
        line.append(&#39;\
&#39;);  

        const cmd = parseCommand(input);  
        switch (cmd) {  
            .List =&gt; {  
                if (std.fs.cwd().openDir(.{}, .{})) |dir| {  
                    var iter = dir.iterate();  
                    while (true) |entry| {  
                        if (entry.file_name.len == 0) continue;  
                        print(\&#34;  {}\
\&#34;, .{entry.file_name});  
                        if (entry.file_name == \&#34;README.md\&#34;) break : \&#34;loop\&#34;;  
                    }  
                    iter.deinit();  
                } else |err| {  
                    print(\&#34;Error listing directory: {s}\
\&#34;, .{@errorName(err)});  
                }  
            },  
            .Cat =&gt; {  
                if (input.len &lt; 2) {  
                    print(\&#34;Usage: /cat &lt;filename&gt;\
\&#34;, .{});  
                    continue;  
                }  
                const filename = input[input.find(&#39;/&#39;) &#43; 1..];  
                if (std.fs.cwd().openFile(filename, .{})) |file| {  
                    var reader = file.reader();  
                    var buffer = std.ArrayList(u8).init(allocator);  
                    while (true) {  
                        const chunk = try reader.readAlloc(allocator, 4096);  
                        try buffer.appendSlice(chunk);  
                        if (chunk.len &lt; 4099) break :\&#34;\&#34; buffer;  
                    }  
                    print(\&#34;{s}\
\&#34;, .{buffer.join(\&#34; \&#34;)});  
                    buffer.deinit();  
                } else |err| {  
                    print(\&#34;Error reading file: {s}\
\&#34;, .{@errorName(err)});  
                }  
            },  
            .Quit =&gt; {  
                print(\&#34;Goodbye!\
\&#34;, .{});  
                return;  
            },  
            .Unknown =&gt; {  
                print(\&#34;Unknown command: {s}\
\&#34;, .{input});  
                continue;  
            }  
        }  
        line.clear();  
    }  
}  

const Command = enum {  
    List,  
    Cat,  
    Quit,  
    Unknown  
};  

fn parseCommand(input: []u8) Command {  
    if (input.len == 0) return .Unknown;  
    if (input[0] == &#39;/&#39;) {  
        if (input == \&#34;/list\&#34;) return .List;  
        if (input == \&#34;/cat\&#34;) return .Cat;  
        if (input == \&#34;/quit\&#34;) return .Quit;  
    }  
    return .Unknown;  
}  


### Key Features:  
- **Interactive Input**: Uses `std.io.getStdIo().reader()` to read lines from standard input.  
- **Command Parsing**: Recognizes `/list`, `/cat`, `/quit`, and other unknown commands.  
- **File System Interaction**: Lists files in the current directory and reads files using `std.fs.cwd()`.  
- **Error Handling**: Properly handles file and directory operations with error sets.  

This example demonstrates the core functionality required for an interactive chat bot with Zig 0.14. You can extend it further by adding more commands or improving error handling.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Okay, but does that code at least compile? Unfortunately not:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ zig build
</span></span><span class="line"><span class="cl">install
</span></span><span class="line"><span class="cl">└─ install foo
</span></span><span class="line"><span class="cl">   └─ zig build-lib foo Debug native <span class="m">1</span> errors
</span></span><span class="line"><span class="cl">error: unable to load <span class="s1">&#39;src/root.zig&#39;</span>: FileNotFound
</span></span><span class="line"><span class="cl">error: the following <span class="nb">command</span> failed with <span class="m">1</span> compilation errors:
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">install
</span></span><span class="line"><span class="cl">└─ install foo
</span></span><span class="line"><span class="cl">   └─ zig build-exe foo Debug native <span class="m">3</span> errors
</span></span><span class="line"><span class="cl">src/main.zig:7:39: error: use of undeclared identifier <span class="s1">&#39;allocator&#39;</span>
</span></span><span class="line"><span class="cl">    var <span class="nv">line</span> <span class="o">=</span> std.ArrayList<span class="o">(</span>u8<span class="o">)</span>.init<span class="o">(</span>allocator<span class="o">)</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                                      ^~~~~~~~~
</span></span><span class="line"><span class="cl">src/main.zig:73:19: error: cannot compare strings <span class="nv">with</span> <span class="o">==</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">(</span><span class="nv">input</span> <span class="o">==</span> <span class="s2">&#34;/list&#34;</span><span class="o">)</span> <span class="k">return</span> .List<span class="p">;</span>
</span></span><span class="line"><span class="cl">            ~~~~~~^~~~~~~~~~
</span></span><span class="line"><span class="cl">/home/akitaonrails/.local/share/mise/installs/zig/0.14.0/lib/std/start.zig:643:46: error: <span class="s1">&#39;main&#39;</span> is not marked <span class="s1">&#39;pub&#39;</span>
</span></span><span class="line"><span class="cl">    const <span class="nv">ReturnType</span> <span class="o">=</span> @typeInfo<span class="o">(</span>@TypeOf<span class="o">(</span>root.main<span class="o">))</span>.@<span class="s2">&#34;fn&#34;</span>.return_type.?<span class="p">;</span>
</span></span><span class="line"><span class="cl">                                         ~~~~^~~~~
</span></span><span class="line"><span class="cl">src/main.zig:3:8: note: declared here
</span></span><span class="line"><span class="cl"><span class="nb">export</span> fn main<span class="o">()</span> !void <span class="o">{</span>
</span></span><span class="line"><span class="cl">~~~~~~~^~~~~~~~~~~~~~~
</span></span><span class="line"><span class="cl">/home/akitaonrails/.local/share/mise/installs/zig/0.14.0/lib/std/start.zig:616:20: note: called from here
</span></span><span class="line"><span class="cl">    <span class="k">return</span> callMain<span class="o">()</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">           ~~~~~~~~^~
</span></span><span class="line"><span class="cl">/home/akitaonrails/.local/share/mise/installs/zig/0.14.0/lib/std/start.zig:571:36: note: called from here
</span></span><span class="line"><span class="cl">    std.posix.exit<span class="o">(</span>callMainWithArgs<span class="o">(</span>argc, argv, envp<span class="o">))</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                   ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
</span></span><span class="line"><span class="cl">error: the following <span class="nb">command</span> failed with <span class="m">3</span> compilation errors:</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>At least I don&rsquo;t think it&rsquo;s my LoRA that broke it: it already couldn&rsquo;t write Zig code that compiled before. This Qwen3 model only has 8B parameters, don&rsquo;t forget. It&rsquo;s much weaker. My LoRA didn&rsquo;t make it worse, it&rsquo;s the same crappy code as before, but at least we have evidence that the training changed the responses more or less like we wanted.</p>
<p>Some things from the training, we can see it did manage to access. This new knowledge <strong>doesn&rsquo;t take up space in the context, unlike a RAG or prompt</strong> and that was the proof of concept: I trained it, managed to load it, got to access that information and, even with a weaker base (8B) and even with a limited context window (8k tokens), it still seems to be working &ldquo;okay-ish&rdquo;, so it&rsquo;s a <strong>semi-success</strong>.</p>
<p>I&rsquo;d need to test training on a bigger model (14B) with more code examples (like those 15MB of its STD source code) and on a bigger GPU with more VRAM (like an A40 or H100).</p>
<h3>Conclusion: Semi-Success?<span class="hx:absolute hx:-mt-20" id="conclusion-semi-success"></span>
    <a href="#conclusion-semi-success" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I decided to publish this post before actually finishing all my test attempts. As you could see, some responses already came with the model answering Zig 0.14.0 stuff. But I still couldn&rsquo;t get good code out of just that. I don&rsquo;t know if it&rsquo;s because the 8B model is too weak for this. Or if my training material could be better formatted. Or if I really was missing making more descriptive and specialized prompts. Or if there are parameters I have to tune (more epochs? higher learning rate? something else?). Anyway, there are MANY possibilities to explore and research.</p>
<p>The goal of this post was more to show in practice how LLM models are TAMPERED WITH post-training, via fine-tuning. You&rsquo;ve &ldquo;heard&rdquo; that it could have this. I&rsquo;m proving that it really does and that you yourself can do it at home. You saw how it can already answer &ldquo;I know Zig 0.14&rdquo; but in practice not necessarily. LLMs do that: they&rsquo;re tuned to give convincing answers. Knowing that this happens, you can be more critical and understand <em>&ldquo;ah, that&rsquo;s alignment, but it actually doesn&rsquo;t know that much.&rdquo;</em> That&rsquo;s the point.</p>
<p>This is also the importance of having open source, open models, because the community can create new trainings on top to remove these alignments, force new alignments (that&rsquo;s why a single model can have so many different versions). For example, on Hugging Face itself there&rsquo;s a whole category of <a href="https://huggingface.co/collections/Umbra-AI/llms-uncensored-6531951e548eae0c99f4a534"target="_blank" rel="noopener">LLMs (Uncensored)</a></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/mom2pmui347tgdwzinkz8kq9wonp" alt="Uncensored"  loading="lazy" /></p>
<p>The most dedicated user is <a href="https://huggingface.co/TheBloke"target="_blank" rel="noopener">TheBloke</a>, follow him to see which LLM he recently unlocked. On his profile there are models like this <a href="https://huggingface.co/TheBloke/CodeLlama-70B-Python-GPTQ"target="_blank" rel="noopener">CodeLlama-70B-Python-GPTQ</a> which, theoretically, was post-trained to be stronger in Python code. And with this post now you have more of an idea of HOW it&rsquo;s done.</p>
<p>This can have many applications. If you&rsquo;re a researcher, maybe you can take it from here as motivation to go deeper where I still haven&rsquo;t gone deeper. Maybe doing specific programming training is harder, maybe you&rsquo;ll discover the trick I still haven&rsquo;t seen. But for prose texts it tends to be better. If your company has a private knowledge base, maybe it makes sense to create a private LoRA that runs on your infra. Anyway, the possibilities are endless.</p>
<p>Whoever figures out the best way to organize this training could create even better programming LoRAs, for Python 3.13, for Elixir 1.18, or for Go 1.24, all newer versions that, at the 2023 cut-offs of commercial LLMs, won&rsquo;t be there.</p>
<p>Why is the data so old? 2023 is already 2 years ago. Because it&rsquo;s hard to clean petabytes of data. If I already struggled to clean 900 kilobytes, imagine PETABYTES. It&rsquo;s not just throwing any data in any way and expecting the model to MAGICALLY learn everything. No: you have to organize, structure, clean this training data. The better that work, the better the chances of the model getting a little better.</p>
<p>It&rsquo;s hundreds of man-hours of manual work, scripts and much more to do this. It makes sense to have a gap of more than 1 year, because then it still takes MONTHS to process all of that using a datacenter of multiple H200s in parallel. A mere 900kb with an 8B parameter model already costs more than 1 hour of my poor 4090. Imagine petabytes paired with a 1 TRILLION parameter model. It&rsquo;s A LOT of processing.</p>
<p>And that&rsquo;s it, everything I wrote in my post was a single day&rsquo;s research, so if someone dedicates more than that, they should get even better results. It&rsquo;s worth studying.</p>
]]></content:encoded><category>llms</category><category>machine-learning</category><category>zig</category></item><item><title>When Do LLMs Fail at Programming? A More Realistic Use Case.</title><link>https://akitaonrails.github.io/en/2025/05/01/when-llms-dont-work-for-coding-a-more-realistic-use-case/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/05/01/when-llms-dont-work-for-coding-a-more-realistic-use-case/</guid><pubDate>Thu, 01 May 2025 17:20:00 -0300</pubDate><description>&lt;p&gt;As I said in my previous rant post about &lt;a href="https://www.akitaonrails.com/en/2025/05/01/rant-llms-vao-evoluir-pra-sempre-desmistificando-llms-na-programacao"target="_blank" rel="noopener"&gt;Demystifying A.I. for programming&lt;/a&gt;, I can confirm that many synthetic benchmarks that claim to measure coding ability are LIES.&lt;/p&gt;
&lt;p&gt;And I&amp;rsquo;m not saying it&amp;rsquo;s a deliberate attempt to lie, but the people doing the research and compiling rankings and leaderboards don&amp;rsquo;t publish the actual details and just drop the result without context. Here&amp;rsquo;s an example I found today: &lt;a href="https://github.com/LiveBench/LiveBench"target="_blank" rel="noopener"&gt;LiveBench&lt;/a&gt;, yet another &amp;ldquo;programming&amp;rdquo; benchmark package. Look at what&amp;rsquo;s actually being tested:&lt;/p&gt;</description><content:encoded><![CDATA[<p>As I said in my previous rant post about <a href="https://www.akitaonrails.com/en/2025/05/01/rant-llms-vao-evoluir-pra-sempre-desmistificando-llms-na-programacao"target="_blank" rel="noopener">Demystifying A.I. for programming</a>, I can confirm that many synthetic benchmarks that claim to measure coding ability are LIES.</p>
<p>And I&rsquo;m not saying it&rsquo;s a deliberate attempt to lie, but the people doing the research and compiling rankings and leaderboards don&rsquo;t publish the actual details and just drop the result without context. Here&rsquo;s an example I found today: <a href="https://github.com/LiveBench/LiveBench"target="_blank" rel="noopener">LiveBench</a>, yet another &ldquo;programming&rdquo; benchmark package. Look at what&rsquo;s actually being tested:</p>
<p><a href="https://github.com/LiveBench/LiveBench/tree/main/livebench/scripts"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/pms3gupxalzq81uwsz73t0ip34m8" alt="LiveBench scripts"  loading="lazy" /></a></p>
<p>They&rsquo;re idiotic tests like &ldquo;load these two CSV files and compare the values&rdquo;. It&rsquo;s even more idiotic than Leet Code. It&rsquo;s even more idiotic than most intern hiring tests. It&rsquo;s the most idiotic set of tests I&rsquo;ve ever seen.</p>
<p>That&rsquo;s why I said my tests are WAY SUPERIOR. Not because I&rsquo;m awesome. But because the bar is extremely low. My most realistic test is a tiny Python project and I give prompts to refactor dirty code and create unit tests. Which is the basics of the basics that I expect from any minimally competent programmer. And I chose Python (which isn&rsquo;t even my favorite language), to give LLMs a better shot, assuming the training material must have a ton of Python.</p>
<p>Even so, they can&rsquo;t do what I asked. You can see it with your own eyes in my comments on the <a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pulls"target="_blank" rel="noopener">pull requests</a> I left in the repository, testing each LLM, multiple times. It&rsquo;s absolutely disappointing to watch.</p>
<h3>New Project: Zig<span class="hx:absolute hx:-mt-20" id="new-project-zig"></span>
    <a href="#new-project-zig" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now I have another theory: LLMs are good at &ldquo;copy and paste&rdquo;, at spitting out code they&rsquo;ve seen repeatedly during training. That&rsquo;s why if you ask anything simple from Leet Code, it knows the answer. It doesn&rsquo;t even need to try to &ldquo;think&rdquo;, it&rsquo;s already baked into the model. It&rsquo;s like taking a test with a cheat sheet.</p>
<p>I think the Achilles&rsquo; heel of LLMs for programming is recently released libraries, new versions, or very new languages. Anything you would struggle to find on Google yourself.</p>
<p>And that&rsquo;s exactly it. I pushed <a href="https://github.com/akitaonrails/qwen-cli-zig"target="_blank" rel="noopener">this new repo to GitHub: Qwen-CLI-Zig</a>
The goal was to try to build, from scratch, a mini interactive chat to talk to a local model, in this case Qwen3:14b GGUF (quantized). And to do that I chose to use the C library <a href="https://github.com/ggml-org/llama.cpp"target="_blank" rel="noopener">llama.cpp</a> directly. I assume every popular higher-level library like litellm, vllm, ollama and similar things either use it, or are inspired by it. It sets more or less the conventions for how LLMs work.</p>
<p>The obstacle: it&rsquo;s C++ code. And to make it worse, you have to compile with the CUDA Toolkit to get access to my GPU. And now my nightmare begins.</p>
<h3>The Problems<span class="hx:absolute hx:-mt-20" id="the-problems"></span>
    <a href="#the-problems" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>For this, I chose to use Aider with Gemini 2.5 Pro Exp Preview 03-25, theoretically the &ldquo;state of the art&rdquo; in programming. &ldquo;According to the benchmarks&rdquo; &hellip;</p>
<p>It&rsquo;s not bad, sure, but what matters for a professional is not praising what you don&rsquo;t know, it&rsquo;s knowing the limitations to figure out how to fix them, create workarounds/hacks or simply know there are cases where you can&rsquo;t use the tool. This is one of those cases.</p>
<p>Here we go:</p>
<ul>
<li>
<p>Gemini clearly didn&rsquo;t have much Zig material to train on, and what it had is out of date or obsolete. It insists on generating code that no longer works. And it doesn&rsquo;t know how to fix it, because it&rsquo;s not in the training.</p>
</li>
<li>
<p>The &ldquo;solution&rdquo; was to run <code>zig build</code>, see the error, go to the <a href="https://ziglang.org/documentation/master/#intCast"target="_blank" rel="noopener">official documentation</a>, for things like <code>ptrCast</code>, <code>intCast</code>, pass the URL to Aider to load it and at least that way it managed to get past those errors.</p>
</li>
<li>
<p>Things that are already well documented, like cloning the llama.cpp repo, configuring the correct flags to compile with CUDA, all that in a bash script, that was easy. It pulled it off flawlessly. I imagine there&rsquo;s a lot of example build scripts for that sort of thing in the training.</p>
</li>
<li>
<p>Even so, in Zig, it needed to load the <code>llama.h</code> header to know which functions exist and their signatures to be able to <code>extern &quot;c&quot;</code> things. Snippets like this:</p>
</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/3t4nq4879ra3k81b4z2clhfb6axh" alt="extern c"  loading="lazy" /></p>
<ul>
<li>
<p>To do that it needs to read <code>vendor/llama.cpp/include/llama.h</code>. It knows this, but kept sending the wrong path the whole time, like <code>vendor/llama.h</code> or <code>vendor/llama.cpp/llama.h</code>. If you don&rsquo;t pay attention, it gets into an infinite loop trying to guess because it errors out endlessly without stopping to think about the correct path. I had to manually tell it the right path, more than once, and even so it would forget.</p>
</li>
<li>
<p>With <code>llama.h</code> loaded into context, it still made rookie mistakes: it went off creating <code>extern</code> for functions it doesn&rsquo;t even need. It read the file and started copy-pasting everything into Zig. And besides padding the code with unnecessary mappings, it got the types WRONG a lot, using <code>c_int</code> for <code>int32</code> (the first is to map to C, the second is just for pure Zig). That cost A LOT of compilation errors and excessive repeated attempts before getting it fixed.</p>
</li>
<li>
<p>After a few frustrated attempts, I got it to create a download script for the Qwen3 14b GGUF model file (it seems llama.cpp, by default, wants GGUF). And finally the program compiled and I managed to run it up to the point where the chat opened and I could submit a message.</p>
</li>
</ul>
<p>And in the final part, it could not, under any circumstances, get the response back from the model. Unfortunately, I chose Zig precisely because it&rsquo;s still very new and I know the syntax keeps changing because it&rsquo;s not a stable language yet. That&rsquo;s why I don&rsquo;t recommend it for real projects. It&rsquo;s very experimental. I still don&rsquo;t know how to use Zig properly. So I didn&rsquo;t have the patience to try to fix the error myself either. (if any Zig person wants to volunteer, I&rsquo;ll accept pull requests).</p>
<p>Anyway, I spent a good 2 hours just on this part. Trying to read documentation. I&rsquo;d try to pass llama.cpp articles with code examples and here comes another problem: everyone only writes blog posts with Python examples (which is the easiest thing, just load litellm), but nobody volunteered to write posts in C++, for example (because nobody who writes posts knows how, myself included).</p>
<p>It&rsquo;s either some specific model config with llama.cpp that I don&rsquo;t know (so the result is wrong) or it&rsquo;s the code that grabs the response that has some bug. I see this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/wocaykokk9w8wj8udgi3hyfk9emj" alt="llama error"  loading="lazy" /></p>
<p>The problem: there&rsquo;s very little documentation about it and I couldn&rsquo;t find anything that helped me solve it. And by trial and error Gemini couldn&rsquo;t solve it either. So I ended the test.</p>
<h3>It&rsquo;s Expensive<span class="hx:absolute hx:-mt-20" id="its-expensive"></span>
    <a href="#its-expensive" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now the bonus. I subscribed to <a href="https://openrouter.ai"target="_blank" rel="noopener">OpenRouter.ai</a> to centralize spending on any LLM in one place. I bought USD 100 in credit and had already spent quite a bit on the refactoring tests with several LLMs. I don&rsquo;t know how much I had left, let&rsquo;s guess around USD 20.</p>
<p>So while doing this new little Zig program, the credits ran out mid-way. My Aider started beeping that there weren&rsquo;t enough credits for the amount of context Gemini generates (this is a problem with &ldquo;thinking&rdquo; models, the reasoning is too long and eats A LOT of token credit).</p>
<p>I went to my account and topped up another USD 100. I came back just to this <code>std::bad_alloc</code> part to try to fix it - and the problem is that Gemini doesn&rsquo;t focus. It starts messing with code at random and things like the model loading procedure onto the GPU, which was already working, it kept frequently breaking. And I kept frequently telling it to revert to how it was. And that burns A LOT of credit too.</p>
<p>The moment I gave up, this is what I saw in my OpenRouter balance:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/pobrdfrwga38utw0jud47no3fhhn" alt="OpenRouter credits"  loading="lazy" /></p>
<p>On top of the ~USD 20 I had left, it spent almost USD 35. Ballparking it, it burned more than USD 50 to fail to deliver working code, after a good 4 or 5 hours of trying. Expect to spend USD 10 per hour in trial and error. If you see yourself repeating &ldquo;What&rsquo;s wrong, fix&rdquo; a lot, it&rsquo;s time to stop and give up, it&rsquo;s not going to manage.</p>
<p>You have to have the right intuition for when information is missing and go fetch URLs on Google to hand to it. You have to have intuition about what&rsquo;s in the training material and know when it doesn&rsquo;t have that information. You have to have intuition to read the stack trace and tell it what to ignore and what to focus on to fix. You have to know that you should ask for one small thing at a time and never &ldquo;fix everything&rdquo; or &ldquo;test everything&rdquo;. It won&rsquo;t manage.</p>
<p>Prompts that have the best chance of working are always very small things. One small block of code at a time. Incremental, testing, and telling it not to touch what already works (and it&rsquo;ll ignore you and touch it, and break it). Having tests to check is fundamental. And &ldquo;compiling&rdquo; isn&rsquo;t enough.</p>
<p>This Zig code &ldquo;compiles&rdquo; but it has a runtime error, it only shows up when it runs. But I didn&rsquo;t have the courage to ask it to write a unit test. All of them were incompetent at writing tests for Python. For Zig then, I already consider it impossible. It won&rsquo;t manage.</p>
<p>Why? Because I think researchers don&rsquo;t know how programmers work. To them, programming is just small scripts (which is what they actually do: open a CSV and aggregate data). That&rsquo;s all they know how to do. And that&rsquo;s what LLMs reflect: they have A LOT of difficulty writing real code. And never longer than about 4 files in context at a time. Any more than that and it gets confused very quickly and starts spitting out VERY wrong code.</p>
<p>By the way, another tip: avoid naming your project with names similar to popular open source projects. Think about it, say your project is called &ldquo;my-react&rdquo;. You&rsquo;re screwed, its training certainly had tons of real React.js examples, and it&rsquo;ll keep confusing the two things all the time. Again, have intuition about what&rsquo;s already in training to help the model not get confused.</p>
<p>And you&rsquo;ll have spent tens of dollars, or more, if you&rsquo;re not careful. And in the end you won&rsquo;t have code that works properly.</p>
<p>Oh right, there are hacks you can pull: integrate a RAG with source code of things you know it doesn&rsquo;t have in training, or train LoRAs and use them together. And even so these aren&rsquo;t &ldquo;solutions&rdquo;, they&rsquo;re attempts to get it to generate code that makes more sense. But on their own, without this kind of help, at this moment they still don&rsquo;t know what to do.</p>
<p>What many people ignore is that the VAST MAJORITY of the main real codebases on the market are all CLOSED. Amazon will never release their e-commerce code, nor will Alibaba. iFood or MercadoLivre the same. No real code is in the model. If it were it would be a huge LAWSUIT risk. They can only use open source code, from places like GitHub or Stack Overflow answers. And there you only find BASIC AND BAD code. So that&rsquo;s all LLMs manage to spit out: BASIC AND BAD code.</p>
<p>If an LLM has been solving your problems, it&rsquo;s because your problem is very simple, not because the model is good.</p>
]]></content:encoded><category>llms</category><category>coding-agents</category><category>zig</category></item></channel></rss>