<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/tags/zig/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:49 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/zig/index.xml" rel="self" type="application/rss+xml"/><item><title>A Resposta sobre Bun em Rust que Andrew Kelly deveria ter feito</title><link>https://akitaonrails.github.io/2026/07/09/resposta-sobre-bun-em-rust-que-andrew-kelly-deveria-ter-feito/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/07/09/resposta-sobre-bun-em-rust-que-andrew-kelly-deveria-ter-feito/</guid><pubDate>Thu, 09 Jul 2026 17:00:00 -0300</pubDate><description>&lt;p&gt;Eu li o post oficial do Jarred Sumner, &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;. E vou começar deixando meu viés claro: eu sou totalmente a favor da decisão do Bun de reescrever em Rust. Não porque Rust ganhou alguma guerra tribal contra Zig. Não é isso. Eu gosto muito de Zig. Mas, para o contexto do Bun, a decisão me parece compreensível, séria e bem executada.&lt;/p&gt;
&lt;p&gt;O que eu mais gostei no post do Jarred foi o nível de detalhe. Ele não vendeu a fantasia idiota de escrever um prompt tipo &amp;ldquo;rewrite Bun in Rust&amp;rdquo; e esperar milagre. Pelo contrário. O que ele descreveu foi procedimento: &lt;code&gt;PORTING.md&lt;/code&gt;, análise de lifetimes, &lt;code&gt;LIFETIMES.tsv&lt;/code&gt;, trial run com três arquivos, workflows dinâmicos, revisores adversariais, correção de processo quando os agentes começaram a fazer besteira com &lt;code&gt;git stash&lt;/code&gt;, depois compiler errors virando fila de trabalho, depois smoke tests, depois suíte inteira, depois CI multi-plataforma. Nada de vibe coding irresponsável. Engenharia usando IA como multiplicador.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu li o post oficial do Jarred Sumner, <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">&ldquo;Rewriting Bun in Rust&rdquo;</a>. E vou começar deixando meu viés claro: eu sou totalmente a favor da decisão do Bun de reescrever em Rust. Não porque Rust ganhou alguma guerra tribal contra Zig. Não é isso. Eu gosto muito de Zig. Mas, para o contexto do Bun, a decisão me parece compreensível, séria e bem executada.</p>
<p>O que eu mais gostei no post do Jarred foi o nível de detalhe. Ele não vendeu a fantasia idiota de escrever um prompt tipo &ldquo;rewrite Bun in Rust&rdquo; e esperar milagre. Pelo contrário. O que ele descreveu foi procedimento: <code>PORTING.md</code>, análise de lifetimes, <code>LIFETIMES.tsv</code>, trial run com três arquivos, workflows dinâmicos, revisores adversariais, correção de processo quando os agentes começaram a fazer besteira com <code>git stash</code>, depois compiler errors virando fila de trabalho, depois smoke tests, depois suíte inteira, depois CI multi-plataforma. Nada de vibe coding irresponsável. Engenharia usando IA como multiplicador.</p>
<p>Por isso fiquei decepcionado com a resposta do Andrew Kelley, <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 é o criador do Zig. Eu queria ler o melhor argumento técnico possível defendendo Zig. Queria ver ele explicar onde Bun confundiu problema de linguagem com problema de arquitetura. Queria ver quais APIs Zig poderiam ter ajudado, quais invariantes deveriam ter sido endurecidas, onde <code>comptime</code>, allocators, arenas, <code>errdefer</code>, <code>DebugAllocator</code> e o novo desenho de I/O do Zig poderiam mudar a análise.</p>
<p>Em vez disso, uma parte grande do texto vira tentativa de assassinato de reputação. Comentário sobre personalidade, gestão, VC, cultura de startup, fofoca de bastidor. Pode até haver mágoa real ali. Não me interessa. Não ajuda tecnicamente.</p>
<p>Então resolvi fazer o exercício que eu esperava ter lido: colocar Claude Fable 5 e GPT 5.6 Sol como árbitros técnicos e pedir a eles a resposta que Andrew deveria ter escrito. Não a resposta gentil. Não a resposta de torcida. A resposta técnica.</p>
<p>Antes de passar a palavra, um off-topic. Se você, como eu, ficou olhando aquelas animações bonitas do GitHub no post do Bun e pensou &ldquo;eu quero isso nos meus projetos&rdquo;, eu fui lá e fiz. O <a href="https://github.com/akitaonrails/github-visualize"target="_blank" rel="noopener"><code>github-visualize</code></a> é um dashboard self-hosted que acompanha seus repositórios e reproduz a evolução dos commits, o heatmap por hora e a corrida do CI até ficar verde. Está tudo aberto no 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="Timeline animada de commits no github-visualize"  loading="lazy" /></a></p>
<p>Mas agora vamos separar apoio de prova. Uma coisa é dizer &ldquo;eu teria feito algo parecido&rdquo;. Outra é dizer &ldquo;era tecnicamente impossível continuar em Zig&rdquo;. Para responder isso, precisamos olhar para causas: quais bugs eram realmente bugs de linguagem, quais eram problemas de arquitetura, quais eram custos de ecossistema, e quais poderiam ter sido reduzidos com uma disciplina diferente dentro do próprio Zig.</p>
<p>É daí que começa a análise.</p>
<p>Antes, vocabulário mínimo. <strong>Use-after-free</strong> é usar memória depois de liberá-la. <strong>Double-free</strong> é liberar a mesma memória duas vezes. <strong>Leak</strong> é esquecer de liberar. <strong>Ownership</strong> é quem é responsável por destruir um recurso. <strong>Lifetime</strong> é por quanto tempo uma referência continua válida. <strong>FFI</strong> é a fronteira onde Rust ou Zig chamam C/C++ e perdem parte das garantias da linguagem. <strong>GC root</strong> é uma forma de dizer ao coletor de lixo: &ldquo;não colete este objeto ainda&rdquo;. <strong>Pinning</strong> é segurar um buffer no lugar para ele não ser movido, destacado ou invalidado enquanto alguém ainda tem um ponteiro para ele. <strong>Reentrância</strong> é quando uma chamada volta para dentro do runtime antes da chamada anterior terminar.</p>
<h2>O que o Rust compra de verdade<span class="hx:absolute hx:-mt-20" id="o-que-o-rust-compra-de-verdade"></span>
    <a href="#o-que-o-rust-compra-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A tese mais forte do post do Bun é simples: uma porcentagem grande dos bugs listados era use-after-free, double-free ou recurso esquecido em caminho de erro. Em Rust seguro, boa parte disso vira erro de compilador ou cleanup automático por <code>Drop</code>, desde que o recurso esteja modelado como valor dono. Rust não prova ausência geral de vazamentos: ciclos de <code>Rc</code>, <code>mem::forget</code>, <code>Box::leak</code> e <code>abort</code> ainda existem. Mas ele torna o vazamento acidental em caminho de erro muito mais difícil.</p>
<p>Isso está correto. E é um argumento forte.</p>
<p>O ponto forte do Rust aqui não é magia. É que ele obriga certas relações de posse e vida útil a aparecerem no tipo, em vez de ficarem espalhadas como convenção. Quando um valor tem dono, quando ele é movido, quando ele sai de escopo, quando um destrutor roda, isso deixa de ser só acordo entre programadores. O compilador participa.</p>
<p>No código atual do Bun, dá pra ver o padrão em miniatura. Um wrapper de bytes do MySQL guarda um valor do JavaScriptCore pinado, e o <code>Drop</code> desfaz o pin quando o objeto morre:</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>Fonte: <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>. O detalhe importante não é a chamada FFI em si. É o cleanup acompanhar o dono do valor.</p>
<p>Esse é o round que Rust ganha limpo. Em um projeto pequeno, disciplina basta. Em meio milhão de linhas, com callback reentrante, threadpool, libuv, sockets, TLS, JavaScriptCore, parser, bundler, package manager e API Node, disciplina vira custo recorrente.</p>
<p>Rust provavelmente reduziu o espaço de estados inválidos que o time precisava manter na cabeça. Vitória real.</p>
<p>Mas vitória real não significa vitória total.</p>
<h2>Zig não estava indefeso<span class="hx:absolute hx:-mt-20" id="zig-não-estava-indefeso"></span>
    <a href="#zig-n%c3%a3o-estava-indefeso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O argumento mais fraco que se pode fazer contra Zig é fingir que ele é C com sintaxe nova. Não é. Zig tem mecanismos explícitos para tratar exatamente parte desses problemas.</p>
<p>O mais óbvio é <a href="https://ziglang.org/documentation/master/#errdefer"target="_blank" rel="noopener"><code>errdefer</code></a>, o mecanismo idiomático para cleanup em caminho de erro:</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>O <a href="https://ziglang.org/documentation/master/#Report-Memory-Leaks"target="_blank" rel="noopener"><code>std.testing.allocator</code> reporta vazamentos</a> em testes. O <a href="https://github.com/ziglang/zig/blob/master/lib/std/heap/debug_allocator.zig"target="_blank" rel="noopener"><code>DebugAllocator</code></a> ajuda a encontrar double-free, leaks e vários usos inválidos de memória, inclusive tornando bugs mais barulhentos por não reutilizar endereços imediatamente. Isso não é a mesma coisa que prova estática de ausência de use-after-free. Não é borrow checker. Não cobre alocação feita dentro de C++, JavaScriptCore, BoringSSL ou qualquer outro allocator externo. Só pega o que passa por ele e só nos caminhos exercitados pelos testes.</p>
<p>O comentário do próprio allocator é preciso sobre o que ele entrega:</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>Fonte: <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>. É ferramenta boa de teste e debug. Não é um teorema.</p>
<p>Mesmo assim, é errado dizer que Zig não tinha ferramenta nenhuma. Tinha.</p>
<p>A diferença é o momento do feedback. Em Rust, muita coisa aparece na compilação. Em Zig, muita coisa aparece em teste, fuzzing, allocator instrumentado, ASAN, revisão e estilo arquitetural. Em código crítico, isso importa. Feedback cedo é melhor.</p>
<p>Mas também importa dizer a frase inteira: se o problema era falta de invariantes claras no código Zig, a resposta poderia ter sido uma reescrita em Rust. Também poderia ter sido uma reestruturação agressiva do Zig.</p>
<p>Não sabemos qual teria sido mais barato. Só sabemos qual o Bun escolheu.</p>
<h2><code>Drop</code> não é só açúcar, mas também não é milagre<span class="hx:absolute hx:-mt-20" id="drop-não-é-só-açúcar-mas-também-não-é-milagre"></span>
    <a href="#drop-n%c3%a3o-%c3%a9-s%c3%b3-a%c3%a7%c3%bacar-mas-tamb%c3%a9m-n%c3%a3o-%c3%a9-milagre" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O post do Bun acerta ao contrastar <code>defer</code> com <code>Drop</code>.</p>
<p>Em Zig, você escreve:</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>Isso é explícito. Está na sua cara. Não há fluxo de controle escondido. Essa é a filosofia do Zig: se algo acontece, você deve conseguir apontar para o lugar onde acontece.</p>
<p>Em Rust, se o tipo implementa <code>Drop</code>, o cleanup acompanha o dono:</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>Isso não é apenas açúcar sintático. As <a href="https://doc.rust-lang.org/reference/destructors.html"target="_blank" rel="noopener">regras de destruição do Rust</a> ligam <code>Drop</code> a ownership, move semantics, ordem de destruição de campos e unwind de panic. Código seguro comum não consegue usar um valor depois de movido, nem dar double-drop no objeto como se ainda fosse dono dele. É uma garantia de linguagem.</p>
<p>Mas a garantia termina onde começa a semântica que o compilador não entende.</p>
<p>Se o <code>drop</code> chama uma função FFI para desproteger um valor do JavaScriptCore, o Rust garante que o destrutor vai rodar nos caminhos normais de escopo e drop, não em <code>abort</code>, <code>mem::forget</code>, leaks intencionais ou ciclos de <code>Rc</code>. Ele não prova que o ponteiro do JSC ainda é válido. Não prova que o GC não tem uma regra especial. Não prova que uma callback assíncrona de C não vai chamar de volta depois que você destruiu o wrapper.</p>
<p>O comentário no mesmo wrapper do Bun mostra o tamanho do contrato que ainda fica fora do compilador:</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>Fonte: <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>. Repara no tamanho do contrato. Ele não sumiu por ser Rust. Ficou documentado e encapsulado.</p>
<p>Essa é a fronteira real: Rust melhora o lado Rust do contrato. Ele não faz mágica dentro do heap do JavaScriptCore.</p>
<h2>A fronteira FFI continua sendo o inferno<span class="hx:absolute hx:-mt-20" id="a-fronteira-ffi-continua-sendo-o-inferno"></span>
    <a href="#a-fronteira-ffi-continua-sendo-o-inferno" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Bun não é uma CLI simples. Bun é runtime JavaScript. Ele conversa com JavaScriptCore, uWebSockets, usockets, BoringSSL, SQLite, bibliotecas de compressão, APIs de sistema, handles assíncronos, buffers que podem ser destacados por JavaScript no meio de uma coerção, callbacks reentrantes e GC conservador.</p>
<p>É aí que o debate fica interessante.</p>
<p>Dentro de um bloco <code>unsafe</code>, Rust exige disciplina. O próprio <a href="https://doc.rust-lang.org/nomicon/ffi.html"target="_blank" rel="noopener">Rustonomicon sobre FFI</a> trata chamada estrangeira e ponteiro cru como obrigações do programador. Se o binding estiver mentindo, se a ownership real não bater com o tipo, se a callback estrangeira sobreviver ao objeto Rust, o compilador não salva.</p>
<p>O binding do Bun documenta as pré-condições na própria declaração:</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>Fonte: <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>. A linguagem permite declarar uma chamada estrangeira como segura. A obrigação de provar essa segurança continua sendo nossa.</p>
<p>Só que existe uma diferença importante: Rust permite encapsular esse <code>unsafe</code> atrás de APIs seguras. Você pode criar tipos de handle, <code>NonNull</code>, <code>PhantomData</code>, <code>Pin</code>, lifetimes marcando relação entre wrapper e runtime, wrappers que não implementam <code>Send</code> ou <code>Sync</code>, RAII para roots do GC, e uma superfície menor para auditoria. O objetivo não é zerar o perigo. É encolher o lugar onde o perigo mora.</p>
<p>O exemplo mais didático do post do Bun é o bug de <code>uv_close</code>. A primeira versão compilava, mas entregava um <code>Box</code> para libuv fechar assincronamente e deixava o <code>Box</code> cair no fim do escopo. Use-after-free seguido de double-free. A versão corrigida no código atual torna a transferência explícita:</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>Fonte: <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>. Compilou em Rust e ainda estava errado na fronteira C/async. O processo de revisão pegou.</p>
<p>Aqui <code>leak</code> não significa &ldquo;vazar para sempre&rdquo;. Significa tirar o <code>Box</code> do controle lexical do Rust e transferir a posse para o protocolo assíncrono do libuv, cuja callback depois reconstitui e libera a alocação.</p>
<p>Zig também permite escrever wrappers bons. A diferença é que ele não tem a fronteira <code>safe</code>/<code>unsafe</code> como parte da linguagem. Em Zig, tudo é explícito e revisável. Em Rust, uma parte maior do contrato pode virar tipo.</p>
<p>Para um time grande, isso pesa. Pesa ainda mais quando parte do código está sendo gerada por agentes.</p>
<h2>Arenas: sim, Bun já usava<span class="hx:absolute hx:-mt-20" id="arenas-sim-bun-já-usava"></span>
    <a href="#arenas-sim-bun-j%c3%a1-usava" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui o argumento pró-Zig precisa ser honesto. Arenas são idiomáticas e excelentes para certos problemas: parser, AST, ciclo de vida de uma requisição, dados que morrem juntos.</p>
<p>Mas o post do Bun não ignora arenas. Ele diz explicitamente que a abordagem existente já era uma mistura de lifetimes por arena, reference counting e &ldquo;pay really close attention&rdquo;. Inclusive cita parser state e AST nodes como bons casos para arena.</p>
<p>O trecho do post é esse:</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>Fonte: <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">post oficial do Bun</a>. O contra-argumento bom não é &ldquo;use arena&rdquo;. Eles já usavam. O contra-argumento bom é perguntar se as fronteiras que sobraram poderiam ter sido endurecidas em Zig.</p>
<p>Então a resposta correta não é &ldquo;vocês esqueceram arena&rdquo;. Eles não esqueceram.</p>
<p>A resposta correta é: arena resolve o caso em que o lifetime é claro. Ela não resolve sozinha handle de socket que fecha assincronamente, callback que roda depois, SSL session que precisa de <code>free</code> específico, root do GC, ponteiro que pode ser invalidado por reentrância, nem objeto que cruza várias camadas de API com ownership implícito.</p>
<p>Arena é uma ferramenta boa. Não é religião.</p>
<p>Se o Bun tivesse endurecido mais as fronteiras em Zig, talvez parte do benefício do rewrite pudesse ter vindo daí: APIs mais restritas, menos ponteiro cru atravessando subsistema, handles com ownership explícito, allocators instrumentados por padrão em teste, fuzzing direcionado para reentrância, wrappers que obrigam <code>deinit</code>, menos &ldquo;presta atenção&rdquo;.</p>
<p>Mas isso também seria uma reescrita. Só que dentro de Zig.</p>
<h2>O borrow checker ajuda, mas não onde a internet acha<span class="hx:absolute hx:-mt-20" id="o-borrow-checker-ajuda-mas-não-onde-a-internet-acha"></span>
    <a href="#o-borrow-checker-ajuda-mas-n%c3%a3o-onde-a-internet-acha" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Muita gente fala de borrow checker como se ele fosse um detector universal de bug. Não é. Borrow checker é o pedaço do compilador Rust que impede certos empréstimos inválidos: dois donos mutáveis ao mesmo tempo, referência viva depois do dono morrer, uso depois de move. É muita coisa. Não é tudo.</p>
<p>Ele não entende o heap do JavaScriptCore. Não entende todas as regras de GC. Não entende automaticamente que uma callback de C pode disparar JS, que pode destacar um <code>ArrayBuffer</code>, que pode reentrar no runtime, que pode invalidar um ponteiro capturado antes. Essas invariantes precisam ser modeladas.</p>
<p>Mas quando são modeladas, Rust cobra.</p>
<p>Um exemplo pequeno no Bun: wrapper de <code>uSockets</code> que transforma ponteiro C em borrow Rust amarrado a <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>Fonte: <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>. O <code>unsafe</code> ainda existe. Mas, depois que o wrapper cria a referência, o borrow checker consegue impedir certos abusos no lado Rust.</p>
<p>E Bun não é só heap JavaScript. É parser, bundler, resolver de módulos, package manager, watcher de arquivos, protocolo HTTP, buffers, caches, tabelas, filas, estado de processo, glue code. Nessas partes, ownership local importa muito. <code>Vec</code>, <code>String</code>, slices, mapas, wrappers, enums, estados finitos: tudo isso fica mais fácil de refatorar com o compilador te batendo na mão.</p>
<p>O argumento honesto não é &ldquo;borrow checker resolve Bun&rdquo;. É: o borrow checker reduz muitas classes de erro no lado Rust do Bun, e isso libera atenção humana para a parte que continua difícil.</p>
<p>É forte o suficiente. Não precisa exagerar.</p>
<h2><code>comptime</code>: aqui Zig tem uma vantagem real<span class="hx:absolute hx:-mt-20" id="comptime-aqui-zig-tem-uma-vantagem-real"></span>
    <a href="#comptime-aqui-zig-tem-uma-vantagem-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O melhor contra-exemplo técnico no próprio post do Bun é <code>Output.pretty</code>.</p>
<p>No Zig, a string de formato podia ser um parâmetro <code>comptime</code>. O próprio post do Bun coloca a diferença assim:</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>Isso permitia processar marcadores de cor e formatação no literal conhecido em tempo de compilação, antes de misturar argumentos runtime. Ao portar para Rust, a primeira versão virou função. Resultado: o parser de tags mexeu em bytes que vinham dos argumentos. O issue <a href="https://github.com/oven-sh/bun/issues/30693"target="_blank" rel="noopener">#30693</a> mostra a regressão no port/canary envolvendo escape OSC 8 de hyperlink e <code>&lt;r&gt;</code>, não uma falha lançada em stable.</p>
<p>O bug era exatamente a contaminação entre template e argumento runtime:</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>Fonte: <a href="https://github.com/oven-sh/bun/issues/30693"target="_blank" rel="noopener">issue #30693</a>.</p>
<p>Depois eles corrigiram usando macro.</p>
<p>O código atual deixa a diferença explícita:</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>E logo abaixo:</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>Fonte: <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>Isso demonstra uma força legítima do Zig: <code>comptime</code> é a própria linguagem rodando em tempo de compilação. Você não muda para um sistema paralelo. Em Rust, macros são poderosas e maduras, mas são outro mecanismo. Para alguns tipos de API, Zig é simplesmente mais direto.</p>
<p>Mas também não vamos transformar um bug de port em prova universal contra Rust. O problema ali não foi &ldquo;Rust é frágil&rdquo;. Foi a tradução errada de uma API <code>comptime</code> para uma função runtime. O próprio post do Bun reconhece isso e corrige com macro.</p>
<p>A leitura correta é mais simples: Zig tinha uma garantia estrutural ali. A primeira versão Rust perdeu essa garantia. Isso custou uma regressão no port/canary.</p>
<p>Ponto para Zig.</p>
<h2>Tamanho de binário e performance: cuidado com causalidade barata<span class="hx:absolute hx:-mt-20" id="tamanho-de-binário-e-performance-cuidado-com-causalidade-barata"></span>
    <a href="#tamanho-de-bin%c3%a1rio-e-performance-cuidado-com-causalidade-barata" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O post do Bun mostra números bons: binário menor, alguns benchmarks mais rápidos, startup melhor em certos cenários.</p>
<p>Os números do 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>Fonte: <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">post oficial do Bun</a>.</p>
<p>Eu acredito nos números. Não acredito em atribuição simples.</p>
<p>Reescrita muda muita coisa ao mesmo tempo. Muda layout de código, remove dívida, troca toolchain, mexe em LTO, muda como generics/comptime são instanciados, muda linker, muda código morto, muda ICU, muda organização interna. Quando tudo isso acontece junto, não dá para olhar o gráfico e dizer: &ldquo;foi Rust&rdquo;.</p>
<p>Também não dá para dizer: &ldquo;foi só rewrite&rdquo;.</p>
<p>O próprio post fala em combinação: Rust, mudanças em ICU, Identical Code Folding, LTO, codegen, limpeza acumulada. LTO é otimização no link final, quando o compilador enxerga mais do programa antes de gerar o binário. Identical Code Folding é o linker juntando funções idênticas em uma só. <a href="https://andrewkelley.me/post/my-thoughts-bun-rust-rewrite.html"target="_blank" rel="noopener">Andrew tem razão</a> em lembrar que Zig também tem suporte a LTO e que parte desses ganhos poderia ter sido buscada antes. Mas isso não prova que o resultado seria igual. Só prova que a causalidade é mista.</p>
<p>O <code>Cargo.toml</code> do Bun mostra que há engenharia de build explícita nessa história:</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>Fonte: <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>E tem configuração de LTO atravessando Rust e 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>Fonte: <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>. Não é &ldquo;Rust venceu&rdquo;. É linguagem, toolchain, linker, configuração e código trabalhando juntos.</p>
<p>Mesma coisa para stack usage e profundidade de parser. O post atribui melhoria ao Rust emitir melhor <a href="https://llvm.org/docs/LangRef.html#llvm-lifetime-start-intrinsic"target="_blank" rel="noopener"><code>llvm.lifetime.start/end</code></a> e permitir reutilização de stack slots. Detalhe de codegen? Sim. Mas detalhe de codegen entregue pelo toolchain atual conta. O próprio Zig ainda tem uma <a href="https://github.com/ziglang/zig/issues/23475"target="_blank" rel="noopener">issue aberta sobre stack allocations que não acabam no fim do escopo</a>. Para o usuário, pouco importa se a vantagem vem da linguagem pura, do compilador ou da maturidade do backend. Importa que funciona hoje.</p>
<p>O mecanismo descrito pelo Bun é este:</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>E o ticket do Zig diz, sem rodeio, que esse caso ainda não está resolvido:</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>Também existe a solução arquitetural: parser iterativo com stack explícita na heap. Funciona em Zig e Rust. Mas é outro trabalho.</p>
<p>Leitura honesta: os números favorecem a versão Rust do Bun naquele experimento. Eles não provam superioridade geral de linguagem.</p>
<h2>Miri, crates e o mundo real<span class="hx:absolute hx:-mt-20" id="miri-crates-e-o-mundo-real"></span>
    <a href="#miri-crates-e-o-mundo-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Há vantagens de Rust que não têm equivalente direto em Zig hoje.</p>
<p>Crates é a óbvia. O ecossistema Rust é grande, maduro e prático. Para um projeto do tamanho do Bun, biblioteca boa na prateleira vale engenharia real. Zig ainda está amadurecendo nesse ponto.</p>
<p><a href="https://github.com/rust-lang/miri"target="_blank" rel="noopener"><code>Miri</code></a> também é vantagem real. Ele detecta classes de comportamento indefinido em código Rust executado: out-of-bounds, use-after-free, dados não inicializados, problemas de alinhamento, data races em certos casos. O próprio projeto documenta os limites: não é prova formal, avalia as execuções que você fornece e tem suporte restrito para FFI e APIs de plataforma. Não substitui teste. Mas para validar abstrações <code>unsafe</code> isoladas, é uma ferramenta que Zig não tem do mesmo jeito.</p>
<p>Bun não só cita Miri no abstrato. Ele está no 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>Fonte: <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>E tem teste interno explicando exatamente o uso:</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>Fonte: <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>. Não prova o programa inteiro. Exercita uma abstração perigosa sob um modelo feito para detectar comportamento indefinido nesses caminhos executados.</p>
<p>Há também contratação, tooling, documentação, IDE, cultura de revisão, CI, exemplos, gente no mercado. Isso não é detalhe político. É engenharia. Linguagem existe dentro de ecossistema.</p>
<p>Se o Bun fosse um projeto menor, com um time menor, menos pressão e menos superfície Node-compatible, talvez eu desse outro peso para isso. Mas Bun virou runtime usado por milhões de downloads mensais. A margem para &ldquo;vamos manter disciplina perfeita&rdquo; diminui.</p>
<h2>Async: Zig talvez tenha uma resposta melhor, mas ainda não é o presente do Bun<span class="hx:absolute hx:-mt-20" id="async-zig-talvez-tenha-uma-resposta-melhor-mas-ainda-não-é-o-presente-do-bun"></span>
    <a href="#async-zig-talvez-tenha-uma-resposta-melhor-mas-ainda-n%c3%a3o-%c3%a9-o-presente-do-bun" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Zig removeu async/await antigo e está <a href="https://kristoff.it/blog/zig-new-async-io/"target="_blank" rel="noopener">redesenhando I/O em torno de <code>std.Io</code></a>, passado pelo chamador de modo parecido com <code>Allocator</code>. A ideia é boa: evitar o function coloring clássico, deixar a função menos acoplada a blocking, threaded ou event-driven, e tratar I/O como capacidade explícita.</p>
<p>O formato proposto é assim:</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>Fonte: <a href="https://kristoff.it/blog/zig-new-async-io/"target="_blank" rel="noopener">Kristoff sobre o novo async I/O do Zig</a>. O <code>io</code> é uma capacidade passada explicitamente, como <code>Allocator</code>.</p>
<p>Eu gosto muito dessa direção.</p>
<p>Mas ela ainda está em transição. A <a href="https://github.com/ziglang/zig/issues/23446"target="_blank" rel="noopener">issue de acompanhamento</a> continua aberta: há protótipo, roadmap, discussão, partes entrando aos poucos. Não é uma base estável sobre a qual o Bun pudesse apostar hoje sem custo.</p>
<p>Então o argumento aqui não pode ser &ldquo;Zig já resolveu&rdquo;. A formulação correta é: Zig está caminhando para um desenho de async/I/O que pode ser mais limpo em alguns aspectos do que o modelo colorido tradicional. Mas Bun precisava tomar decisão com o que existe agora, não com o que promete existir depois.</p>
<p>Isso é sempre cruel com linguagens jovens. Mas é real.</p>
<h2>O processo do rewrite importa mais que a linguagem<span class="hx:absolute hx:-mt-20" id="o-processo-do-rewrite-importa-mais-que-a-linguagem"></span>
    <a href="#o-processo-do-rewrite-importa-mais-que-a-linguagem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O melhor do post do Jarred não é Rust. É método.</p>
<p>O loop mental do post é literalmente isso:</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>Depois, compiler errors viraram fila:</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>E o ciclo por crate era explícito:</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>Fonte: <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">post oficial do Bun</a>.</p>
<p>Ele não mandou Claude reescrever um runtime inteiro e foi dormir. Ele montou loops. Separou implementador de revisor. Usou contexto diferente para revisão adversarial. Fez trial run. Corrigiu o workflow quando os agentes começaram a se atropelar. Proibiu comandos perigosos. Dividiu worktrees. Transformou compiler errors em fila. Rodou a mesma suíte TypeScript porque a suíte não dependia da linguagem do runtime. Usou CI multi-plataforma como árbitro.</p>
<p>Essa é a parte que todo mundo deveria estudar.</p>
<p>O modelo não substituiu engenharia. O modelo executou trabalho dentro de um processo de engenharia. Quando o processo estava ruim, o resultado ficava ruim. Quando o processo melhorava, o resultado melhorava.</p>
<p>É o detalhe que a discussão tribal ignora. A história não é &ldquo;Claude reescreveu Bun&rdquo;. A história é &ldquo;um engenheiro transformou uma migração gigante em vários loops verificáveis e usou Claude para acelerar cada loop&rdquo;.</p>
<p>Muito mais interessante.</p>
<h2>Então Zig falhou?<span class="hx:absolute hx:-mt-20" id="então-zig-falhou"></span>
    <a href="#ent%c3%a3o-zig-falhou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Não.</p>
<p>Zig não falhou por Bun ter escolhido Rust. Rust também não virou resposta universal porque Bun escolheu Rust.</p>
<p>O que a evidência pública mostra é mais chato: aquele código, naquele momento, estava caro demais para evoluir com o nível de confiança que o time queria. Rust comprou previsibilidade. Comprou feedback de compilador. Comprou ecossistema. Comprou um safe/unsafe boundary explícito. Comprou tooling. Comprou um ambiente em que agentes de IA conseguem bater em compiler errors e avançar mecanicamente.</p>
<p>Basta para justificar a decisão.</p>
<p>Mas não é suficiente para provar que Zig era tecnicamente inviável para Bun. Muitos problemas citados parecem também problemas de fronteira: ownership implícito, APIs permissivas demais, alocação sem contrato visível, recursos que cruzam camadas sem tipo carregando o dono, testes que talvez não capturavam invariantes difíceis, e o custo humano de revisar tudo isso.</p>
<p>Essas coisas poderiam ter sido atacadas em Zig. Talvez uma reestruturação agressiva do Zig tivesse resolvido muita coisa. Talvez não. Talvez custasse mais que o port. Talvez custasse menos, mas entregasse menos confiança. Não temos o contrafactual.</p>
<p>E é exatamente por isso que a conclusão precisa ser calibrada.</p>
<p>Eu apoio a decisão do Jarred como decisão de produto e engenharia. O post dele é um dos relatos mais úteis que já vi sobre rewrite grande assistido por IA. Mas gostar da decisão não me obriga a fingir que ela era a única conclusão técnica possível.</p>
<p>E tem um disclaimer importante. A <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">Anthropic comprou o Bun em dezembro de 2025</a>, e o próprio Jarred abre o post dizendo que ele e o time do Bun agora trabalham lá. A Anthropic vende Claude Code. Jarred tinha todo o incentivo institucional para usar o máximo possível de Claude e transformar essa reescrita numa demonstração pública do produto do novo empregador. Isso não prova má-fé, nem invalida um resultado técnico. Mas adiciona viés à decisão e à forma como o caso é apresentado. Ignorar esse conflito de incentivos seria tão ingênuo quanto ignorar a mágoa do Andrew com a saída do Bun de Zig.</p>
<p>A melhor leitura é menos dramática: Bun escolheu pagar um custo grande agora para reduzir custos recorrentes de manutenção depois. Rust foi uma escolha defensável, talvez ótima para aquele time naquele momento. Zig continuou sendo uma linguagem excelente, mas teria exigido outro tipo de reengenharia: mais invariantes explícitas, menos ownership informal, mais ferramentas ligadas por padrão, mais fronteiras endurecidas.</p>
<p>Andrew poderia ter escrito isso. Poderia ter defendido Zig no campo técnico. Poderia ter dito: &ldquo;Rust comprou algumas garantias reais, mas boa parte do problema era arquitetura e processo; aqui está como Zig poderia ter atacado as mesmas causas.&rdquo; Esse teria sido um post útil.</p>
<p>Não precisamos declarar vencedor. Linguagem não substitui arquitetura. Ela só muda quais erros ficam baratos de detectar e quais continuam dependendo de disciplina humana.</p>
<p>Bun escolheu trocar uma parte dessa disciplina por compilador. Eu acho uma boa aposta.</p>
<p>Não acho que isso enterre Zig.</p>
]]></content:encoded><category>linguagens-de-programacao</category><category>rust</category><category>zig</category></item><item><title>Ensinando Zig mais recente pra sua LLM - Treinando LoRas (quase)</title><link>https://akitaonrails.github.io/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase/</guid><pubDate>Sat, 03 May 2025 02:30:00 -0300</pubDate><description>&lt;p&gt;&lt;a href="https://www.akitaonrails.com/2025/05/01/quando-llms-nao-funcionam-pra-programar-um-caso-de-uso-mais-real"target="_blank" rel="noopener"&gt;Dois posts atrás&lt;/a&gt; eu demonstrei o Calcanhar de Aquiles das LLMs pra programação: eles não foram treinados com os dados mais recentes disponíveis. Então se perguntar sobre mudanças na versão da sua biblioteca, linguagem ou frameworks favoritos que saiu mês passado, ele não vai saber.&lt;/p&gt;
&lt;p&gt;Nota: todos os códigos que vou mostrar mais abaixo estão disponíveis &lt;a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener"&gt;neste repositório no meu GitHub&lt;/a&gt;. Fucem lá depois de ler até o fim.&lt;/p&gt;
&lt;p&gt;Por exemplo, eu perguntei ao modelo Qwen3-8b (que saiu poucos dias atrás), qual versão de Zig ele sabe e a resposta foi: 0.11 ou 0.12:&lt;/p&gt;</description><content:encoded><![CDATA[<p><a href="https://www.akitaonrails.com/2025/05/01/quando-llms-nao-funcionam-pra-programar-um-caso-de-uso-mais-real"target="_blank" rel="noopener">Dois posts atrás</a> eu demonstrei o Calcanhar de Aquiles das LLMs pra programação: eles não foram treinados com os dados mais recentes disponíveis. Então se perguntar sobre mudanças na versão da sua biblioteca, linguagem ou frameworks favoritos que saiu mês passado, ele não vai saber.</p>
<p>Nota: todos os códigos que vou mostrar mais abaixo estão disponíveis <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">neste repositório no meu GitHub</a>. Fucem lá depois de ler até o fim.</p>
<p>Por exemplo, eu perguntei ao modelo Qwen3-8b (que saiu poucos dias atrás), qual versão de Zig ele sabe e a resposta foi: 0.11 ou 0.12:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/39h50oguk03tci9reclcbiovolhm" alt="zig antigo"  loading="lazy" /></p>
<p>Aliás, é assim como na foto que se chama uma API compatível com OpenAI de LLMs com curl. De qualquer forma, foi por isso que eu sofri tanto pra tentar fazer funcionar um programinha simples (nível &ldquo;Hello World&rdquo;) ontem: ele não sabe que já estamos no <a href="https://ziglang.org/download/0.14.0/release-notes.html"target="_blank" rel="noopener">Zig 0.14</a>. E foi de propósito, eu sabia que ele não ia ter essa informação.</p>
<p><em>&ldquo;E outros LLMs comerciais? Eles não são melhores que os open source?&rdquo;</em></p>
<p>Perguntei ao ChatGPT 4.1, Claude Sonnet 3.7, Gemini 2.5 Pro Experimental, Deepseek R1 Zero (tudo de uma vez, OpenRouter.ai permite chat multi-model):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/z9wbo5yi49m4vd3zi3pjpxbo7zj7" alt="LLM cut off"  loading="lazy" /></p>
<p>Resumindo:</p>
<ul>
<li>ChatGPT 4.1 diz que ele só tem dados até Junho de 2024 e só conhece até Zig 0.11.</li>
<li>Claude Sonnet diz que ele só tem dados até Abril de 2023 e só conhece até 0.10.</li>
<li>Gemini 2.5 Pro Exp diz que só tem dados até começo de 2023 e só conhece até Zig 0.11 também</li>
<li>Deepseek R1 (depois de deep thinking &hellip;) respondeu curto e grosso que só tem dados até Outubro de 2023 e só conhece até Zig 0.11.</li>
</ul>
<p><em>&ldquo;Por que eles não tem dados mais novos?&rdquo;</em></p>
<p>Vou responder isso demonstrando os motivos nas próximas seções.</p>
<p><em>&ldquo;Mas ele consegue puxar informações da internet, então não importa&rdquo;</em></p>
<p>Importa. Essas informações não estão NO modelo. Quando você pede pra pesquisar na Internet, o modelo não sabe fazer isso. Ele foi instruído (na fase de fine-tuning/alinhamento ou via prompts) pra pedir ao sistema de chat - o parser - que pegue essa requisição, navegue na web (pense um curl, playwright ou qualquer coisa assim), puxe o conteúdo do site, copia e cola só na sua sessão de chat (é como uma solução de RAG). Ele só tem aquela informação naquela sessão e outros chats não são influenciados por isso.</p>
<p><em>&ldquo;Mas como que quando ele responde, ele mostra os links de onde veio essa informação?&rdquo;</em></p>
<p>Ele - provavelmente - não tem isso dentro do modelo. O que eu imagino é mais simples. Eles tem catalogado e indexado todo o material de treinamento original (incluindo a URL das fontes) num banco de dados (pense Elastic, Postgres com full-text-search, ou um banco vetorial qualquer). Quando termina de responder no chat - o programa de chat e não o modelo -, pesquisa esse trecho nesse banco de dados por um trecho parecido o suficiente e dali pega as URLs das fontes pra te mostrar.</p>
<p>Enfim.</p>
<p>Zig é experimental, pré-1.0. A sintaxe de tudo muda MUITO de uma versão pra outra, porque ainda estão experimentando o que é melhor. E ainda vai mudar mais. Ou seja: é impossível fazer &ldquo;Vibe Coding&rdquo; de Zig a menos que você fixe seu compilador pra versão 0.11 ou 0.12. Mas isso seria &ldquo;tosco&rdquo;, pra dizer o mínimo. Eu escolhi tentar Zig de propósito, exatamente pra provar esse ponto.</p>
<p>Mesma coisa vale pra muita empresa com código privado. Penso empresas de ERP que tem linguagens próprias, bibliotecas próprias, e nada disso é público, então garantidamente não estava no treinamento de uma OpenAI e certamente é muito mais informação que cabe num contexto de prompt.</p>
<p><strong>&ldquo;Ah, basta fazer um RAG&rdquo;</strong></p>
<p>Solução enlatada sem entender. Não, não adianta. Ele vai dar prioridade ao treinamento que teve. Segundo, pra cada coisa que pedir, você precisaria saber que vai precisar de algo que mudou na 0.14, pesquisar o trecho da documentação e ficar adicionando no contexto do chat. Se for fazer isso método a método, pra que está usando o chat? Vai e escreve direto o código, vai ser mais rápido.</p>
<p>Jogar TODA a documentação no contexto não adianta. Eu fiz a conta: baixei a documentação inteira e release notes são 200 mil caracteres, aproximadamente uns 50 mil tokens. Maioria dos LLMs open source vai estourar a janela (média de 32 ml tokens). Via API/Chat comercial você iria pagar por token, à toa. Imagine querer refatorar um código seu, de 100 linhas e ter que subir 200 mil linhas de contexto. Nunca faça isso. O custo-benefício não compensa.</p>
<p><strong>&ldquo;Ah, mas os Gemini da vida suporta 1 milhão de tokens&rdquo;</strong></p>
<p>Sim, com sliding window, já repeti várias vezes. Ele não consegue dar atenção a tudo de uma só vez. De novo: você vai <strong>PAGAR</strong> por esses 50 mil tokens, tá? E não vai ser barato. Imagina, toda nova sessão ter que subir 50 mil tokens que não tem garantia de funcionar.</p>
<p>Então ph0deu? Não tem como usar Zig com 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>O hype de RAG (Retrieval Augmented Generation, que é só um nome bonitinho) faz parecer que é algo &ldquo;mágico&rdquo;. Mas realmente, não tem nada demais. Ele é bom pra quando você tem um banco de dados grande, que cresce o tempo todo, mas em cada sessão de chat, você só precisa de um pouquinho dessa informação. Pense um suporte de e-commerce da vida, com tickets de suporte. Cresce todo dia, mas pra cada chamada você só precisa do ticket específico da pessoa que está atendendo, que é bem curto.</p>
<p>Do jeito mais rudimentar, o atendente pesquisa o ticket no tradicional Zendesk da vida, copia o texto, joga na janela de chat do ChatGPT e começa a fazer perguntas sobre esse ticket, tipo &ldquo;pode me resumir qual o problema desse cliente?&rdquo;, &ldquo;consegue imaginar uma solução pra ele?&rdquo; e assim por diante.</p>
<p>Um RAG é um BANCO DE DADOS com todos esses tickets pré-indexados pra facilitar a procura por relevância, e depois inserir o trecho que achou direto na janela de contexto. Serve pra automatizar, PULAR os passos de pesquisar manualmente em outro sistema e dar copy e paste, mas o efeito é EXATAMENTE o mesmo.</p>
<p><strong>&ldquo;Ah, mas usa VECTOR databases, deve ser mais ph0da&rdquo;</strong></p>
<p>Ph0da é ouvir asneira. Poderia ser um Postgres e fazer um SELECT pra achar o ticket, é exatamente a mesma coisa. Se você habilitar &ldquo;full-text-search&rdquo; no Postgres ele vai indexar seus tickets em um &ldquo;VECTOR SPACE MODEL&rdquo;, que é essencialmente, um VECTOR DATABASE.</p>
<p>Se em vez disso prefere SOLR ou ELASTIC, ambos são VECTOR DATABASES especializados em pesquisa de texto.</p>
<p><em>Duh, the more you know.</em></p>
<p>O principal:</p>
<ul>
<li>
<p>quanto mais entuchar no contexto (seja prompt texto, seja &ldquo;embedding&rdquo; de vector database com RAG - é a mesma coisa), mais arrisca confundir o LLM e ele começar a responder alucinações. Os melhores prompts são estruturados, organizados, com ordens diretas, e que explicam o contexto claramente.</p>
</li>
<li>
<p>seja texto-prompt seja &ldquo;embedding&rdquo;, vai CONSUMIR TOKENS de input. E se a janela for pequena (modelos open source), vai estourar se não controlar o tamanho do que mandar de input. Não tem mágica.</p>
</li>
</ul>
<p>Então qual a saída? Apesar de só falarem nisso. Não existe só RAG no mundo de LLM.</p>
<h3>Possível Solução: LoRas<span class="hx:absolute hx:-mt-20" id="possível-solução-loras"></span>
    <a href="#poss%c3%advel-solu%c3%a7%c3%a3o-loras" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se em vez de coisas que crescem diariamente, for informação mais &ldquo;permanente&rdquo; como manuais de instrução, livros, talvez RAGs não sejam a melhor forma. Porque vai ser sempre MUITO GRANDE e precisaria REPETIR A MESMA COISA em toda sessão de chat. Especialmente regras que dependem de outras regras.</p>
<p>Sim, bons providers tem <a href="https://x.com/pashmerepat/status/1918084120514900395"target="_blank" rel="noopener">CACHE STATS</a> e isso ajuda muito. Mesmo assim não é garantia. Do nada você vai ver sua conta e se surpreender com USD 500 do nada gasto em crédito de tokens.</p>
<p>Exemplo: documentação de linguagens de programação. Como é meu caso com Zig 0.14.0. Eu gostaria que a LLM tivesse esse contexto, mas toda vez subir 50 mil tokens, é muita coisa se eu só quiser corrigir um arquivo de 50 linhas. Ninguém pensa nessas coisas e nem calcula tamanho do contexto vs tamanho da interação no chat/api, e deveria: qualquer tokens desperdiçado é crédito gasto na sua conta.</p>
<p>Existe uma forma de pegar um modelo LLM que já existe e adicionar o treinamento que falta por cima, sem usar espaço de contexto. Seria como se o modelo fosse &ldquo;atualizado&rdquo; pra saber Zig 0.14. Isso são LoRas (<strong>Low-Rank Adapter</strong>). É um tipo de treinamento menor que podemos adicionar. Treinamos pequenas matrizes adicionais (adapters), que se anexam aos layers da LLM original. Quando carregado isso atualiza as saídas do modelo sem precisar refazer todo o treinamento completo do zero (que é a parte mais cara e que leva meses em datacenters pesados da OpenAI, por exemplo).</p>
<p>E fica um <strong>DISCLAIMER</strong>:</p>
<pre><code>Eu não estudei ainda o suficiente sobre todos os parâmetros de treinamento. Treinar é justamente a fase mais difícil. Em particular: gerar material de treinamento de QUALIDADE e &quot;tunar&quot; os parâmetros do treinamento. Tem gente especializada só nisso e não existe uma única receita que serve pra tudo. É quase uma &quot;arte&quot; e precisa de muito mais dedicação do que eu fiz só pra este post.
</code></pre>
<p>Alguns artigos pra começar a estudar:</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>E tem muito mais. É aqui que tem que entender mais os &ldquo;internals&rdquo; de verdade, as redes neurais, função de perda, gradientes, etc. Não subestime o treinamento. Não vai funcionar de cara na primeira vez, nem na décima. É MUITA tentativa e erro e ajuste.</p>
<p>O resultado final não é exatamente a mesma coisa que um treinamento completo, mas esses adapters modificam os parâmetros dos layers do modelo pré-existente, alterando o resultado final pra coisas específicas que queremos (que ele saiba Zig 0.14). É BEM mais barato que um treinamento do zero. Não garante que vai funcionar 100%, mas dá uma opção pra testarmos, que pode ser mais robusto que um RAG.</p>
<p>O processo é uma versão miniatura de um treinamento de modelo de verdade. Temos que resolver os seguintes passos:</p>
<ul>
<li>conseguir o material de treinamento (ex. documentação)</li>
<li>escrever prompts de fine-tuning (eu preciso &ldquo;ensinar&rdquo; como ele deve responder - essa é a parte mais difícil)</li>
<li>precisa tokenizar essa informação (trivial), carregar o modelo de base (fácil), e começar a treinar (quanto melhor a GPU, menos lento)</li>
<li>se der sucesso precisa subir esse LoRa junto com o modelo de base</li>
<li>apontar ferramentas como Aider pra esse novo modelo modificado.</li>
</ul>
<p>Vamos lá, passo a passo.</p>
<h3>1. Material de Treinamento<span class="hx:absolute hx:-mt-20" id="1-material-de-treinamento"></span>
    <a href="#1-material-de-treinamento" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu acho que esta é a parte mais difícil: conseguir material de treinamento de qualidade pra treinar.</p>
<p>No caso de alinhamento, isso é feito construindo na mão (com a ajuda de scripts, com a ajuda de outra LLM pra ajudar a gerar textos sintéticos), mas o ideal seria eu tentar construir um arquivão JSON com chaves de &ldquo;prompt&rdquo; e &ldquo;completion&rdquo; parecido com:</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>Agora, fazer isso pra CADA UMA das novas funcionalidades da versão 0.14.0, incluindo exemplos de código, múltiplas vezes pra cada tópico, imaginando formas diferentes de perguntar. E não, eu não fiz isso não pretendo fazer, já é muito mais trabalho do que eu quero tentar só no meu experimento. E eu nem sei se essa é a melhor forma de fazer ainda. Como eu disse antes, é praticamente uma &ldquo;arte&rdquo;.</p>
<p>O arquivo de <a href="https://ziglang.org/download/0.14.0/release-notes.html"target="_blank" rel="noopener">RELEASE NOTES</a> convertido em Markdown tem ridículos <strong>107 mil caracteres</strong>. Eu ia passar o resto da semana só re-editando ele em forma de prompts. Não vou fazer isso. Em vez disso, vou só passar o texto crú inteiro e torcer pra esses novos fatos &ldquo;grudarem&rdquo; o suficiente pra LLM se lembrar delas. Pra puxar, é simples:</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>Já que estou no embalo, porque não fazer a mesma coisa com a documentação do Zig? Felizmente a documentação é um único arquivão HTML. Então não tem trabalho de fazer um web scrapper recursivo nem nada disso. É o mesmo procedimento:</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>Isso deu um Markdown de <strong>464 kilobytes</strong>. Como eu acho que só isso não é suficiente. Achei bom também pegar os exemplos de código que estão no diretório <code>zig/doc</code> que clonei direto do <a href="https://github.com/ziglang/zig/tree/master/doc/langref"target="_blank" rel="noopener">repositório oficial do Zig</a>. Não sei qual a melhor maneira, mas eu tentei organizar um único arquivão, com nome do arquivo <code>.zig</code> e o código delimitado por uma tag, com um prompt no começo indicando isso:</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>Notem que neste caso eu estou formatando cada exemplo começando com &ldquo;Reference filename: {filename}&rdquo; e depois adicionando o código num bloco de Markdown com 3 tremas. É um bom formato? Não sei ainda. Fazer material de treino É DIFÍCIL.</p>
<p>Isso resultou em mais um arquivão de <strong>152 kilobytes</strong>. Espero que não confuda mais do que ajude. E não parei nisso. Eu queria colocar o código-fonte inteiro da <a href="https://github.com/ziglang/zig/tree/master/lib/std"target="_blank" rel="noopener">Standard Library</a> e cheguei a fazer o script, baixar e compilar em um formato estruturado, mas o arquivão final deu <strong>15 MEGABYTES</strong>. Infelizmente tive que comentar pra fora do meu script de treinamento. Agora eu bato numa limitação da minha RTX 4090: não tem VRAM suficiente pra treinar tudo isso.</p>
<p>Como já repeti antes, a 4090 só tem 24GB de VRAM. O modelo Qwen3-8b sozinho ocupa 16GB (não, número de parâmetros influencia tamanho mas não significa que 1 parâmetro é 1 byte sempre). Tem que sobrar memória pra fazer os cálculos todos do treinamento, e ele vai gastar 21GB enquanto treina. Se eu coloco esse material de 15MB junto, acontece isso:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/lawgc11lm8bv08hc2u4ms62v4hm0" alt="out of memory vram"  loading="lazy" /></p>
<p>Pra treinar com tudo isso, tem que ser remoto, num RunPod com a A40 como já mostrei em outro blog post, no mínimo. Ou com GPUs mais rápidas como a H100, com 80GB, e mais potência pra acabar um pouco mais rápido. Então, eu já estou limitado em material de treinamento de qualidade e agora limitado na quantidade de material que posso usar.</p>
<p>Já que não pude usar o código-fonte (que teria exemplos reais de código idiomático da versão 0.14.0), procurei outros exemplos pra adicionar e achei esse repositório <a href="https://github.com/zigcc/zig-cookbook"target="_blank" rel="noopener">Zig Cookbook</a>, que é organizado em problemas num arquivo Markdown a soluções em código compatível com 0.14. Fiz esse outro script pra organizar tudo num único arquivo:</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>Neste caso estou tentando ver se usar CHAT TAGS faz diferença. Não é todo modelo que suporta. Mas o Aider, por exemplo, monta os prompts assim: com tags que indicam &ldquo;quem está falando&rdquo; (system, user, assistant) e parece que é uma forma alternativa a fazer em formato JSON. Daí cada problema/código fica assim:</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>Vai funcionar bem assim? Não sei 😆 Como falei, preciso gastar muito mais horas me dedicando só a entender boa formatação de material de treinamento.</p>
<p>Por fim, tentei fazer um arquivo curto de prompts como falei no começo, pra tentar forçar o chat a responder que sabe da existência da versão 0.14 e quais são as mudanças nessa versão (só os tópicos do release notes).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ir04nsralua23ada3wk45xvavm97" alt="training prompts"  loading="lazy" /></p>
<p>É assim que se faz o chamado <strong>&ldquo;alinhamento&rdquo;</strong>. Numa empresa grande com OpenAI, eles devem ter CENTENAS de seres humanos pra escreverem perguntas e respostas que querem forçar a LLM a responder. E temos que fazer a mesma coisa aqui, caso contrário o treinamento de Zig 0.11 e 0.12 que já tem no modelo base pode ainda se sobressair ao material novo que estou adicionando.</p>
<p>Entendo que não tem um jeito trivial de dizer <em>&ldquo;ignora a versão antiga e só puxa do meu novo material&rdquo;</em>. Não é assim que funciona. Você &ldquo;influencia&rdquo; o treinamento (<strong>treinamento supervisionado</strong>), mas não tem &ldquo;garantias&rdquo; que todas as variações de perguntas vai dar a mesma resposta, a menos que você inpute a maioria das variações manualmente no alinhamento.</p>
<pre><code>É por isso que eu falo pra NÃO ACREDITAR numa LLM. Ela não está RACIOCINANDO. Ela foi FORÇADA a responder de uma determinada forma por humanos, no treinamento. Literalmente tem milhões de RESPOSTAS PRÉ-PROGRAMADAS já embutidas no treinamento. É assim que você CONTROLA as respostas.
</code></pre>
<p>No meu caso, não basta só subir a documentação, eu deveria dizer nos prompts &ldquo;quais as mudanças de linguagem no Zig 0.14.0?&rdquo; e dar a resposta &ldquo;No Zig 0.14.0 tivemos as seguinte mudanças de linguagem: 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; (sim, mudou bastante coisa mesmo).</p>
<p>Enfim. vou subir meu procedimento de treinamento <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">no meu GitHub</a>. Lá tem um diretório <code>raw_data</code> que é onde fica o material que compilei. Tem scripts como <code>concat-zig-docs.py</code>, <code>concat-zig-stdlib.py</code> ou <code>concat-zig-cookbook.py</code> pra concatenar cada grupo de material num único arquivão como expliquei. E no final, tem o <code>train-zig.py</code> que de fato carrega todos os datasets e faz o treinamento.</p>
<p>Tem outro fator importante: comparado com os mais de 800 kilobytes de material que compilei, os prompts que mencionei ainda são muito pequenos, só 8.3 kilobytes. Precisamos tentar fazer <strong>OVER-SAMPLING</strong>. O ideal seria escrever pelo menos 10x mais prompts reais manualmente, mas eu realmente não estou com tanta paciência. O que vou fazer é repetir esses meus prompts várias vezes:</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>É assim que se carrega os dados de treinamento como <code>datasets</code>. E eu uso <code>random_choice</code> do Numpy pra faze over-sampling do arquivo JSON e deixar ele artificialmente ficar maior, repetindo as linhas de forma aleatória. E no final temos um <code>combined_dataset</code> que é tudo concatenado, cerca de quase 900 kilobytes de material.</p>
<p>Depois leia o arquivo <code>train_lora.py</code>  mas tendo o dataset combinado, agora é a parte do treinamento em si:</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>Primeiro, tokenizamos o dataset, convertemos texto em tokens (isso é rápido, no chat que você usa todo dia, seu texto é convertido em tokens assim também - o modelo não entende texto, entende embeddings, que são esses tokens traduzidos em vetores).</p>
<p>Em <code>SFTConfig</code> configuramos os parâmetros do treinamento. Acho que maior parte são valores padrão, mas tem dois importantes:</p>
<ul>
<li>
<p>Epoch: Uma passada pelo material de treinamento é chamado de &ldquo;Um Epoch&rdquo;. Dizem que pra pouco material (como meu caso), é bom passar mais de 3 Epochs, até 10 Epochs. Mais Epochs tenta fazer meu novo LoRa &ldquo;grudar&rdquo; mais. Se o dataset fosse ordens de grandeza maior, era melhor menos Epochs, porque existe o problema de <strong>OVER-FITTING</strong>. Ele &ldquo;gruda&rdquo; em excesso e o modelo vai memorizar demais meu material e só dar copy e paste direto. Depende do tipo de material, depende da quantidade de material. Com 5 Epochs esse material leva mais de 1 hora pra processar, 10 Epochs aí puxa pra mais de 2 horas.</p>
</li>
<li>
<p>Learning Rate: é o fator de escala de quanto queremos que os &ldquo;pesos&rdquo; dentro do modelo mudem. Quão agressivamente queremos que essa nova informação mude os pesos do modelo, pra ele realmente &ldquo;aprender&rdquo;? Se for alto demais, ele vai começar a &ldquo;esquecer&rdquo; o que já tinha no treinamento anterior e criar buracos de conhecimento (&ldquo;catastrophic forgetting&rdquo;). Se for baixo demais, muda pouco, e nosso LoRa não vai ter nenhum efeito significativo.</p>
</li>
</ul>
<p>Tem mais um parâmetro que me deixa em dúvida <code>max_seq_length</code> que serve pra truncar texto longo demais. Teoricamente a biblioteca <code>unsloth</code>sabe dividir o material de treinamento em &ldquo;chunks&rdquo; menores (se tiver delimitadores óbvios). Mas eu não sei o que acontece com coisas gigantes, como a documentação toda. Se ele divide e usa, ou se trunca e não usa. São muitos detalhes que precisa estudar mais ainda. Se ele estiver truncando sem eu saber, talvez eu esteja dando material que ele não está usando, por exemplo.</p>
<p>Os fatos antigos que já existem no treinamento (Zig 0.11 e 0.12) são mais fortes do que o meu material. Então subir esses parâmetros pode ajudar a influenciar mais o resultado final depois. É a outra parte da &ldquo;arte&rdquo;: ficar ajustando esses valores pra melhor encaixar o novo conhecimento. Num treinamento completo, do zero, é ainda mais importante. Eu não sei tudo sobre isso. O que eu falei é o nível amador de treinar uma LLM.</p>
<p>Um LoRa é pra ser pequeno. Tendo um modelo base de 8B, com esse tamanho de material de treinamento, o Lora final vai ter na faixa de 3 a 4GB (não sei se isso é considerado grande ou pequeno). E isso vai ter que ser o suficiente pra re-direcionar as respostas pro novo conhecimento. Se não sabia disso, em sites com Hugging Face você vai encontrar LoRas pré-treinados pra usar, não precisa necessariamente fazer do zero se já existe um pronto.</p>
<p>Com o material no lugar, tratado, organizado, basta rodar meu <code>train_lora.py</code> e vai ter algo assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/s3rpq8b6a3h1p0qt60yazf042cr7" alt="training"  loading="lazy" /></p>
<p>E com tantos Epochs e Learning Rate alto, vai demorar! Na minha RTX 4090 deve levar <strong>mais de 1 hora treinando</strong> (com 5 Epochs), talvez mais (não cronometrei), e enquanto isso vai chumbar a GPU mesmo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ivjodz630t9svrpvofuzpmgjanhe" alt="GPU usage"  loading="lazy" /></p>
<p>Sei que existem serviços online pra treinar Loras remotos, acho que a própria Hugging Face tem APIs pra isso, ou podemos subir uma GPU mais potente num RunPod da vida. Obviamente, precisa ter acesso ao modelo pra treinar por cima (os parâmetros do LoRa são dependentes do modelo base) então não tem como fazer um LoRa de GPT 4.1 na sua máquina. Aí depende se a OpenAI tem APIs pra treinar LoRas remotamente (acho que tem, não procurei saber).</p>
<p>Repetindo: LoRas são específicos pro modelo base. Digamos que fez um LoRa pro modelo Qwen3-8b (meu caso). Esse LoRa vai funcionar pro Qwen3-32b? Não. É específico. Tem que carregar exatamente o mesmo modelo base na hora de usar seu LoRa.</p>
<p>Fica a seu critério, não precisa ter uma GPU local só pra isso. É um processo intenso, que usa muito recurso e muita energia e, dependendo de quanto material de treinamento e quantas epochs &hellip; vai tomar um café.</p>
<p>No final, vai surgir um diretório <code>qwen3-zig-lora</code> mais ou menos assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/83h7fwsx0jsod3h17ruv4ag237r1" alt="lora"  loading="lazy" /></p>
<p>Ele grava um checkpoint por epoch ou algo assim, pelo que entendi. Significa que dependendo da estratégia, se precisar dá pra parar o treinamento e depois continuar de onde parou. Eu não tentei fazer isso, mas imagino que num treinamento de verdade, que demora MESES, isso é essencial.</p>
<h3>Mais um Problema - Limitações do Ollama<span class="hx:absolute hx:-mt-20" id="mais-um-problema---limitações-do-ollama"></span>
    <a href="#mais-um-problema---limita%c3%a7%c3%b5es-do-ollama" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra usar um modelo local, como já demonstrei <a href="https://www.akitaonrails.com/2025/04/25/seu-proprio-co-pilot-gratuito-universal-que-funciona-local-aider-ollama-qwen"target="_blank" rel="noopener">neste outro artigo</a> podemos carregar um LLM como um Qwen3 com o servidor Ollama, que vai subir um servidor web e servir APIs no padrão da OpenAI. Daí é só apontar uma ferramenta como Aider pra ela e pronto, temos um assistente de código rodando local.</p>
<p>O problema: o suporte a LoRas no Ollama é limitado (pode ser falta de pesquisa minha, mas não achei rápido uma solução). Eles usam um formato proprietário pros modelos, e não arquivos <code>.safetensors</code> como os que tem numa Hugging Face. Enfim, esbarrei nesse problema e acho que não dá pra prosseguir fácil com Ollama.</p>
<p>Então temos que trocar e, felizmente, existem opções. A mais popular alternativa é o <a href="https://blog.vllm.ai/2023/06/20/vllm.html"target="_blank" rel="noopener">vLLM</a>. Ele faz a mesma coisa que: carrega modelos e sobe um servidor HTTP de API no padrão da OpenAI. A vantagem é ser mais flexível (funciona com modelos da Hugging Face), mais avançado, mais customizável e o principal: consegue subir <a href="https://docs.vllm.ai/en/latest/features/lora.html"target="_blank" rel="noopener">modelos com LoRas</a>. Veja a documentação no link anterior, mas no meu caso acho que é bem simples.</p>
<p>E pra quem pensa em fazer deploy pra múltiplos usuários, o vLLM é mais performático e mais rápido que o Ollama também. Mesmo pra soluções de RAG: o Ollama só aceita injeção de prompt, já vLLM vai integrar RAG melhor via OpenAI/proxy/Langchain por exemplo.</p>
<p>Pode precisar estudar um pouquinho mais as opções, mas tecnicamente falando, ele é superior. Eu sempre penso em Ollama mais pra rodar local na sua própria máquina, mas não pra produção. Já vLLM eu &ldquo;acho&rdquo; que seria melhor pra esse propósito - vale pesquisar depois se tiver esse interesse.</p>
<p>Meu treinamento, no final, vai gerar um diretório chamado <code>qwen3-zig-lora</code> e com vLLM podemos subir o server assim:</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>Pra testar que subiu, podemos rodar um comando simples de curl:</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>E vamos ter o lora listado como se fosse um modelo qualquer. veja o <code>ziglora</code>lá embaixo:</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>Pra conversar com ele, podemos diretamente usar a API com curl e mandar a mensagem pro Lora:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/f1etshmornpoawgci7ndb7791r3a" alt="curl lora"  loading="lazy" /></p>
<p>E prestem atenção na resposta. Eu perguntei qual a versão mais recente de Zig que ele tem familiaridade e a resposta foi <em>&ldquo;Eu estou familiarizado com a linguagem Zig até a versão 0.14.0 de 2025.&rdquo;</em></p>
<p>Não sei dizer se é verdade 😂 isso só significa que o peso do meu over-sampling de prompts com respostas enlatadas está funcionando. Talvez ele não tenha aprendido nada de Zig 0.14.0 mas aprendeu a responder que sabe. E, de novo, por isso você não deve confiar em LLMs: ter a resposta enlatada forçada no alinhamento, não significa que ele realmente &ldquo;saiba&rdquo; do que está falando, eu só forcei ela a responder com convicção que sim. E essa parte funcionou.</p>
<p>O Qwen3-8b tem uma janela de contexto minúscula, só 8K de tokens. Então no <code>.aider.model.settings.yaml</code> tem que configurar assim:</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>Pra rodar o Aider, temporariamente estou fazendo assim:</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>Como é uma API compatível com da OpenAI, só aponto pra localhost na variável de ambiente. E como é um modelo vLLM em vez de &ldquo;openai/&hellip;&rdquo; tem que usar &ldquo;hosted_vllm/&hellip;&rdquo;. Também pra economizar tokens, estou desligando repo-maps do Aider e tentando desligar o thinking do modemo com <code>-reasoning-effort none</code> mas não sei se funciona, ainda vejo ele fazendo uns pensamentos longos assim.</p>
<p>Eu preferia ter usado o modelo Qwen3-14b mas como falei, não ia caber na minha 4090, com a 8B já estou no limite, e eu não estava com paciência pra subir numa RunPod. Fora que eu não tinha idéia quantas vezes ia precisar repetir o treinamento (até o momento escrevendo este post, já repeti pelo menos 4x, cada uma levando mais de 1 hora). Pelo menos rodando local sei que não estou sendo cobrado caro por algum serviço online. Depois, se funcionar mesmo, tento com um modelo maior, mais material, numa máquina mais potente com mais VRAM pra caber.</p>
<h3>Tangente: Aprendendo uma Linguagem<span class="hx:absolute hx:-mt-20" id="tangente-aprendendo-uma-linguagem"></span>
    <a href="#tangente-aprendendo-uma-linguagem" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu suspeito que meu material de treinamento é ainda muito fraco. Vamos pensar:</p>
<p>Se você só ler a documentação (digamos que memorize), se você só ler pequenos exemplos, como o Cookbook (digamos que memorize), se só ler um release note. No final podemos dizer que agora você é sênior de Zig? Dificilmente. Pra LLM também não é simples assim. Precisa de MUITO mais material.</p>
<p>Um exemplo seria ler todo o código-fonte da Standard Library, pra realmente saber como cada estrutura, cada função realmente funciona por baixo dos panos. E a ainda assim é pouco.</p>
<p>Precisaria fazer um scrapper pra filtrar o StackOverflow inteiro procurando por problemas e soluções de Zig 0.14. Pontos de dor na conversão de código de 0.12 pra 0.14. O máximo que puder. Você aprende assim: tendo um problema e buscando outra pessoa que teve o mesmo problema no passado, pra ver como resolveu.</p>
<p>Precisaria buscar todas as Issues no repositório. Reportes de bugs ou falsos positivos de não entendimento da documentação, por exemplo. Como foram resolvidos é a parte importante.</p>
<p>Precisaria buscar mais ainda que só um Cookbook. Se tiver livros, cursos, exercícios, precisaria organizar tudo isso no treinamento.</p>
<p>Mais do que isso, precisaria saber C/C++ da mesma forma, precisaria conhecer os compiladores GCC e Clang. Precisaria saber todos os problemas de C/C++ que o Zig diz que melhora, em vários exemplos de código. Aprender Zig no nível sênior não é só Zig, é todo o resto do ecossistema.</p>
<p>Precisaria ver como, por exemplo, em Rust se resolvem vários problemas e ter soluções semelhantes em Zig, pra ter ponto e comparação, especialmente em problemas difíceis que envolvam otimização de performance, gerenciamento de memória, gerenciamento de concorrência, segurança, etc.</p>
<p>Precisaria ir atrás dos principais projetos open source que usam Zig novo, classificar que problemas resolver, ler todo o código-fonte. Tudo isso precisaria estar no material de treinamento, igual um programador também teria que fazer pra subir de amador pra avançado, de júnior pra sênior.</p>
<p>Eu não sei que materiais foram usados num Qwen (ou Claude, ou Gemini) pra treinar Zig. Espero que tenha sido pelo menos isso. Senão ele realmente não vai ser capaz de fazer código Zig de verdade.</p>
<p>Só que tanto material assim começa a chegar na categoria de gigabytes de dados pra treinar. Já não saberia dizer se cabe continuar usando LoRas ou se já entra em categoria de criar checkpoints em cima do modelo mesmo (tudo no mesmo &ldquo;arquivão&rdquo; da LLM). São dúvidas que ainda não tenho resposta.</p>
<h3>1as Impressões do LoRa<span class="hx:absolute hx:-mt-20" id="1as-impressões-do-lora"></span>
    <a href="#1as-impress%c3%b5es-do-lora" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Infelizmente, como eu usei o Qwen3-8b como base, também fiquei limitado à janela de contexto dele que é super curta, só 8192 tokens. Daí não dá pra usar o Aider, porque só o system prompt pra ele instruir o modelo já consome quase tudo e não sobra espaço mais pra ele responder. Então meus testes foram limitados porque precisei conversar com o modelo direto pela API, usando <code>curl</code> e <code>jq</code> pra ver as respostas.</p>
<p>É pra isso que serve aquele <code>--max-model-len</code> na chamada pra subir o vLLM, senão ele tenta subir uma janela default grande demais (40k) e estoura a VRAM da minha RTX 4090:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/5sb1dj2x0st18wsbyecwef7lbwk6" alt="vllm out of memory"  loading="lazy" /></p>
<p>Percebam como estou mandando prompt super curtos: não tem contexto adicionar de RAG nem nada disso pra tentar ensinar nada de Zig 0.14. Tudo que ele responder está permanentemente no treinamento da LoRa agora.</p>
<p>Repetindo o primeiro teste:</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>Eis a resposta (só a mensagem, eu tirei do JSON pra não ficar longo aqui):</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>Veja como é devolvido a parte do &ldquo;deep thinking&rdquo; ou &ldquo;reasoning&rdquo;. Esse modelo faz isso e é ruim porque a janela de contexto é pequena e esses tokens de &ldquo;think&rdquo; ocupam espaço no contexto. Veja como ele conseguiu responder que sabe 0.14 e até algumas das novidades nele. Vamos continuar:</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>E eis a resposta:</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>A resposta em si parece bem ok. Ele não truncou, não saiu aparecendo caracter em chinês (isso aconteceu num dos meus treinamentos que fracassaram). Vamos comparar com o release note oficial do site:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/fuks9uthx1l56sfpcltclhlx4n2h" alt="release note splat"  loading="lazy" /></p>
<p>Sendo honesto, eu não sei dizer se ele está correto (eu não sei Zig ainda também 🤣). Mas de bater o olho eu acho que ele inventou lero-lero, não parece correto.</p>
<p>Agora vamos ver o teste mais difícil: tentar fazer um programinha.</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>A resposta foi BEM LONGA (sorte que não chegou a estourar o contexto). Vou deixar na íntegra pra vocês verem como o modelo com o lora carregado conseguiu responder:</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>Tá, mas esse código pelo menos compila? Infelizmente não:</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>Pelo menos eu não acho que meu LoRa que quebrou: ele já não conseguia fazer código que compilava em Zig antes. Esse modelo Qwen3 tem só 8B parâmetros, não esqueça. Ele é bem mais fraco. Meu LoRa não piorou, tá a mesma porcaria de código que antes, mas pelo menos temos evidências que o treinamento mudou as respostas mais ou menos como queríamos.</p>
<p>Algumas coisas do treinamento, podemos ver que ele conseguiu acessar. Esse conhecimento novo <strong>não ocupa espaço no contexto, diferente de um RAG ou prompt</strong> e essa era a prova de conceito: treinou, conseguiu carregar, deu pra acessar essa informação e, mesmo com uma base mais fraca (8B) e mesmo com janela de contexto limitada (8k tokens), ele parece ainda estar funcionando &ldquo;direitinho&rdquo; então é um <strong>semi-sucesso</strong>.</p>
<p>Precisaria testar treinar num modelo maior (14B) com mais exemplos de código (como aqueles 15MB de código fonte da STD dele) e numa GPU maior com mais VRAM (como uma A40 ou H100).</p>
<h3>Conclusão: Semi-Sucesso?<span class="hx:absolute hx:-mt-20" id="conclusão-semi-sucesso"></span>
    <a href="#conclus%c3%a3o-semi-sucesso" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Resolvi publicar este post antes de realmente acabar todas as minhas tentativass de testes. Como puderam ver, algumas respostas já vieram com o modelo respondendo coisas da versão Zig 0.14.0. Mas eu não consegui ainda obter um bom código só com isso.  Não sei se é porque o modelo 8B é fraco demais pra isso. Ou se meu material de treinamento poderia ser melhor formatado. Ou se faltou mesmo fazer mais prompts mais descritivos e especializados. Ou se foram parâmetros que tenho que tunar (mais epochs? mais learning rate? outra coisa?). Enfim, são MUITAS possibilidades pra explorar e pesquisar.</p>
<p>O objetivo deste post foi mais mostrar na prática como que modelos LLM são ADULTERADOS pós-treinamento, via fine-tuning. Vocês já &ldquo;ouviram falar&rdquo; que poderia ter isso. Estou provando que tem mesmo e que você mesmo pode fazer em casa. Viram como ele já consegue responder &ldquo;Eu sei Zig 0.14&rdquo; mas na prática não necessariamente. LLMs fazem isso: são tunados pra dar respostas convincentes. Sabendo que isso acontece, você pode ser mais crítico e entender <em>&ldquo;ah, isso é alinhamento, mas ele na verdade não sabe tanto assim.&rdquo;</em> Esse é o ponto.</p>
<p>Essa também é a importância de existir modelos open source, abertos, porque a comunidade consegue criar novos treinamentos por cima pra remover esses alinhamentos, forçar novos alinhamentos (por isso um mesmo modelo pode ter tantas versões diferentes). Exemplo, na Hugging Face mesmo tem uma categoria só de <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>O usuário mais dedicado é o <a href="https://huggingface.co/TheBloke"target="_blank" rel="noopener">TheBloke</a> sigam ele pra ver qual ele LLM ele desbloqueou recentemente. No perfil dele tem modelos como esse <a href="https://huggingface.co/TheBloke/CodeLlama-70B-Python-GPTQ"target="_blank" rel="noopener">CodeLlama-70B-Python-GPTQ</a> que, teoricamente, foi pós-treinado pra ser mais forte em código Python. E com este post agora você tem mais noção de COMO se faz isso.</p>
<p>Isso pode ter muitas aplicações. Se você é um pesquisador, talvez consiga pegar a partir daqui como motivação pra aprofundar onde eu ainda não me aprofundei. Talvez fazer treinamento específico de programação seja mais difícil, talvez você descubra o pulo do gato que eu ainda não vi. Mas pra textos dissertativos ele costuma ser melhor. Se sua empresa tem uma base de conhecimento privada, talvez faça sentido criar um Lora privado que roda na sua infra. Enfim, as possibilidades são inúmeras.</p>
<p>Quem descobrir a melhor forma de organizar esse treinamento, poderia criar LoRas de programação ainda melhores, pra Python 3.13, pra Elixir 1.18, ou pra Go 1.24, todas versões mais novas que, nos cut-off de 2023 dos LLMs comerciais, por isso não vai ter.</p>
<p>Porque os dados são tão antigos? 2023 já faz 2 anos. Porque é difícil limpar petabytes de dados. Se eu já apanhei pra limpar 900 kilobytes, imagina PETABYTES. Não é só sair jogando qualquer dado de qualquer jeito e achar que MAGICAMENTE o modelo vai aprender tudo. Não: precisa organizar, estruturar, limpar esses dados de treinamento. Quanto melhor for esse trabalho, mais chances do modelo ficar um pouco melhor.</p>
<p>São centenas de horas-homem de trabalho manual, scripts e muito mais pra fazer isso. Faz sentido ter um gap de mais de 1 ano, porque depois ainda vai levar MESES pra processar tudo isso usando um datacenter de várias H200 da vida em paralelo. Meros 900kb com um modelo de 8B de parâmetros já custa mais de 1 hora da minha pobre 4090. Imagina petabytes pareado com um modelo de 1 TRILHÃO de parâmetros. É MUITO processamento.</p>
<p>E é isso, tudo isso que escrevi no meu post foi pesquisa de 1 único dia, então se alguém se dedicar mais que isso, deve ter resultados ainda melhores. Vale a pena estudar.</p>
]]></content:encoded><category>llms</category><category>machine-learning</category><category>zig</category></item><item><title>Quando LLMs não Funcionam pra Programar? Um caso de uso mais real.</title><link>https://akitaonrails.github.io/2025/05/01/quando-llms-nao-funcionam-pra-programar-um-caso-de-uso-mais-real/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/05/01/quando-llms-nao-funcionam-pra-programar-um-caso-de-uso-mais-real/</guid><pubDate>Thu, 01 May 2025 17:20:00 -0300</pubDate><description>&lt;p&gt;Como eu disse no meu post anterior de Rant sobre &lt;a href="https://www.akitaonrails.com/2025/05/01/rant-llms-vao-evoluir-pra-sempre-desmistificando-llms-na-programacao"target="_blank" rel="noopener"&gt;Desmistificar a I.A. pra programação&lt;/a&gt;, posso confirmar que muitos benchmarks sintéticos que dizem medir capacidade de programação, são MENTIRAS.&lt;/p&gt;
&lt;p&gt;E não digo que é uma tentativa proposital de mentir, mas que quem está fazendo as pesquisas e compilando rankings e leaderboards, não divulgam exatamente os detalhes e só soltam o resultado sem contexto. Este é um exemplo que achei hoje: &lt;a href="https://github.com/LiveBench/LiveBench"target="_blank" rel="noopener"&gt;LiveBench&lt;/a&gt;, que é mais um pacote de benchmark de &amp;ldquo;programação&amp;rdquo;. Olha que é testado exatamente:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Como eu disse no meu post anterior de Rant sobre <a href="https://www.akitaonrails.com/2025/05/01/rant-llms-vao-evoluir-pra-sempre-desmistificando-llms-na-programacao"target="_blank" rel="noopener">Desmistificar a I.A. pra programação</a>, posso confirmar que muitos benchmarks sintéticos que dizem medir capacidade de programação, são MENTIRAS.</p>
<p>E não digo que é uma tentativa proposital de mentir, mas que quem está fazendo as pesquisas e compilando rankings e leaderboards, não divulgam exatamente os detalhes e só soltam o resultado sem contexto. Este é um exemplo que achei hoje: <a href="https://github.com/LiveBench/LiveBench"target="_blank" rel="noopener">LiveBench</a>, que é mais um pacote de benchmark de &ldquo;programação&rdquo;. Olha  que é testado exatamente:</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>São testes idiotas do tipo &ldquo;carregue estes dois arquivos CSV e compare os valores&rdquo;. É ainda mais idiota que Leet Code. É ainda mais idiota que a maioria dos testes de contratação de programadores estagiários. Enfim, é o conjunto mais idiota de testes que eu já vi.</p>
<p>Por isso eu disse que meus testes são MUITO SUPERIORES. Não porque eu sou ph0da. Mas porque a régua é extremamente baixa. Meu teste mais real é um projetinho pequeno em Python e eu dou prompts de refatorar código sujo e criar testes unitários. Que é o básico do básico que eu espero de qualquer programador minimamente competente. E escolhi Python (que nem é minha linguagem favorita), pra dar mais chance pras LLMs, imaginando que no material de treinamento deve ter visto muito Python.</p>
<p>E nem assim eles conseguem fazer o que eu pedi. Vocês podem ver com seus próprios olhos nos meus comentários das <a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pulls"target="_blank" rel="noopener">pull requests</a> que deixei no repositório, testando cada LLM, múltiplas vezes. É absolutamente decepcionante de ver.</p>
<h3>Projeto Novo: Zig<span class="hx:absolute hx:-mt-20" id="projeto-novo-zig"></span>
    <a href="#projeto-novo-zig" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora eu tenho outra teoria: LLMs são boas em &ldquo;copy e paste&rdquo;, em cuspir código que ele viu repetidas vezes no treinamento. Por isso que se perguntar qualquer coisas simples de Leet Code, ele sabe responder. Nem precisa tentar &ldquo;pensar&rdquo;, já está embutido no modelo. É que nem fazer uma prova com cola.</p>
<p>Eu acho que o calcanhar de aquiles de LLM pra programação são bibliotecas recém-lançadas, novas versões ou linguagens muito novas. Qualquer coisa que se procurar no Google, você mesmo vai ter dificuldade de achar.</p>
<p>E é exatamente isso. Eu subi <a href="https://github.com/akitaonrails/qwen-cli-zig"target="_blank" rel="noopener">este novo repositório no GitHub: Qwen-CLI-Zig</a>
O objetivo foi tentar fazer, do zero, um mini chat interativo pra conversar com um modelo local, no caso o Qwen3:14b GGUF (quantizado). E pra fazer isso escolhi usar direto a biblioteca em C <a href="https://github.com/ggml-org/llama.cpp"target="_blank" rel="noopener">llama.cpp</a>. Eu imagino que toda biblioteca popular de mais alto nível como litellm, vllm, ollama e coisas assim ou usam, ou se inspiram nessa biblioteca. Ela estabelece mais ou menos as convenções de como LLMs funcionam.</p>
<p>O obstáculo: é código em C++. E pra piorar, tem que compilar com o CUDA Toolkit pra ter acesso à minha GPU. E agora começa meu pesadelo.</p>
<h3>Os Problemas<span class="hx:absolute hx:-mt-20" id="os-problemas"></span>
    <a href="#os-problemas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra fazer isso, escolhi usar Aider com o Gemini 2.5 Pro Exp Preview 03-25, teoricamente o dito &ldquo;estado da arte&rdquo; na programação. &ldquo;Segundo os benchmarks&rdquo; &hellip;</p>
<p>De fato, ele não é ruim, mas o importante pra um profissional não é ficar elogiando o que não sabe, e sim saber as limitações pra descobrir como consertar, criar workarounds/gambiarras ou simplesmente saber que tem casos que não dá pra usar a ferramenta. Esse é um desses casos.</p>
<p>Vamos lá:</p>
<ul>
<li>
<p>O Gemini claramente não teve muito material de Zig pra treinar e o que teve está defasado ou obsoleto. Ele insiste em criar código que não funciona mais. E não sabe como consertar, porque não tem no treinamento.</p>
</li>
<li>
<p>A &ldquo;solução&rdquo; foi rodar <code>zig build</code>, ver o erro, ir na <a href="https://ziglang.org/documentation/master/#intCast"target="_blank" rel="noopener">documentação oficial</a>, pra coisas como <code>ptrCast</code>, <code>intCast</code>, passar a URL pro Aider carregar e pelo menos assim ele conseguia passar por esses erros.</p>
</li>
<li>
<p>Coisas que já são bem documentadas, como clonar o repositório do llama.cpp, configurar os flags corretos pra compilar com CUDA, tudo isso num script de bash, isso foi fácil. Ele tirou de letra. Exemplos de scripts de build dessas coisas imagino que teve muito no treinamento.</p>
</li>
<li>
<p>Mesmo assim, no Zig, ele precisava carregar o header <code>llama.h</code> pra saber que funções existem e que assinaturas pra conseguir fazer <code>extern &quot;c&quot;</code> das coisas. Trechos como este:</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>Pra fazer isso ele precisa ler de <code>vendor/llama.cpp/include/llama.h</code>. Ele sabe disso, mas mandava o path errado o tempo todo, como <code>vendor/llama.h</code> ou <code>vendor/llama.cpp/llama.h</code>. Se não prestar atenção, ele fica em loop infinito tentando adivinhar porque dá erro sem parar pra pensar no path correto. Eu que tive que manualmente, mais de uma vez, dizer pra ele o path correto e mesmo assim ele esquecia.</p>
</li>
<li>
<p>Com o <code>llama.h</code> no contexto carregado, mesmo assim ele fazia erro de estag: saía criando <code>extern</code> pra funções que nem vai precisar. Ele leu o arquivo e saiu copiando e colando no Zig tudo. Mas além de encher linguiça com mapeamento desnecessário, ele errava MUITO os tipos, errava <code>c_int</code> por <code>int32</code> (o primeiro é pra mapear com C, o segundo é só pra Zig puro). Isso custou MUITOS erros de compilação e tentativas repetidas em excesso até conseguir consertar.</p>
</li>
<li>
<p>Depois de algumas tentativas frustradas, consegui fazer ele criar um script de download do arquivo de modelo do Qwen3 14b GGUF (parece que o llama.cpp, por default, quer GGUF). E finalmente o programa compilou e eu consegui rodar até o ponto onde abria o chat e eu conseguia submeter alguma mensagem.</p>
</li>
</ul>
<p>E na parte final, ele não conseguiu, de jeito nenhum pegar a resposta de volta do modelo. Infelizmente, eu escolhi Zig justamente por ser super novo ainda e eu sei que a sintaxe fica mudando porque não é uma linguagem estável ainda. Por isso não recomendo pra projetos de verdade. É muito experimental. Eu não sei usar Zig direito ainda. Então também não tive paciência pra tentar consertar o erro eu mesmo. (se alguém de Zig se voluntariar, aceito pull requests).</p>
<p>Enfim, gastei umas boas 2 horas só nessa parte. Tentando ler documentação. Eu tentava passar artigos de llama.cpp com exemplos de código e aqui vem outro problema: todo mundo só faz blog post de exemplos de Python (que é a coisa mais fácil, só carregar litellm), mas ninguém se voluntariou pra escrever posts em C++, por exemplo (porque ninguém que escreve posts sabe como, eu incluso).</p>
<p>É ou alguma configuração específica do modelo com llama.cpp que eu não sei (daí o resultado é errado) ou é o código que pega a resposta que tem algum erro. Eu vejo isso:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/wocaykokk9w8wj8udgi3hyfk9emj" alt="llama error"  loading="lazy" /></p>
<p>O problema: tem pouquíssima documentação a respeito e não achei nada que me ajudasse a resolver. E só na tentativa e erro o Gemini também não conseguiu resolver. Então eu encerrei o teste.</p>
<h3>Custa Caro<span class="hx:absolute hx:-mt-20" id="custa-caro"></span>
    <a href="#custa-caro" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora o bônus. Eu assinei a <a href="https://openrouter.ai"target="_blank" rel="noopener">OpenRouter.ai</a> pra centralizar os gastos de qualquer LLM num lugar só. Comprei USD 100 de crédito e já tinha gasto bastante nos testes de refatoração de várias LLM. Não sei quanto tinha sobrando, vamos chutar uns USD 20.</p>
<p>Daí fazendo esse novo programinha em Zig, acabou os créditos no meio do caminho. Meu Aider começou a apitar que faltava créditos pro tanto de contexto que o Gemini gera (esse é um problema de modelos &ldquo;thinking&rdquo;, o pensamento é longo demais e come MUITO crédito de tokens).</p>
<p>Fui na minha conta e enchi mais USD 100. Voltei só nessa parte do <code>std::bad_alloc</code>pra tentar consertar - e o problema é que o Gemini não foca. Ele sai mexendo código aleatoriamente e coisas como o procedimento de carga do modelo na GPU, que já estava funcionando, ele ficava frequentemente quebrando. E eu ficava frequentemente mandando ele voltar como tava. E isso gasta MUITO crédito também.</p>
<p>No momento que desisti, é isso que vi no meu saldo na OpenRouter:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/pobrdfrwga38utw0jud47no3fhhn" alt="OpenRouter credits"  loading="lazy" /></p>
<p>Além dos cerca de USD 20 que eu tinha sobrando, ele gastou quase USD 35. Chutando, ele gastou mais de USD 50 pra não me dar um código que funciona, depois de umas boas 4 ou 5 horas tentando. Pode esperar gastar USD 10 por hora em tentativa e erro. Se você vê que está repetindo muito &ldquo;What&rsquo;s wrong, fix&rdquo;, é hora de parar e desistir, ele não vai conseguir.</p>
<p>Você tem que ter a intuição correta de quando falta informação e buscar URLs no Google pra dar pra ele. Você tem que ter intuição do que tem no material de treinamento e saber que ele não tem essa informação. Você tem que ter intuição de ler o stacktrace e dizer o que ele deve ignorar e o que ele deve focar pra corrigir. Você tem que saber que é pra pedir uma coisa pequena de cada vez e nunca &ldquo;corrija tudo&rdquo; ou &ldquo;teste tudo&rdquo;. Ele não vai conseguir.</p>
<p>Prompts que tem mais chance de funcionar são sempre coisas bem pequenas. Um pequeno bloco de código de cada vez. Incremental, testando, e mandando não mexer no que já funciona (e ele vai te ignorar e mexer, e quebrar). Ter testes pra checar é fundamental. E não adianta &ldquo;compilar&rdquo;.</p>
<p>Esse código de Zig &ldquo;compila&rdquo; mas ele tem erro de run-time, só aparece quando roda. Mas eu não tive coragem de pedir pra ele fazer um teste unitário. Todos foram incompetentes em fazer testes pra Python. Pra Zig então, eu já acho que é uma impossibilidade. Ele não vai conseguir.</p>
<p>Por que? Porque eu acho que pesquisadores não sabem como programadores trabalham. Pra eles, programação são só pequenos scripts (que é o que eles fazem mesmo: abrir um CSV e mandar agregar dados). É só isso que sabem fazer. E é isso que as LLMs refletem: ele tem MUITA dificuldade de fazer código de verdade. E nunca mais longo que uns 4 arquivos no contexto de cada vez. Mais que isso e ele fica confuso muito rápido e começa a cuspir código MUITO errado.</p>
<p>Aliás, outra dica: evite criar seu projeto com nomes parecidos com de projetos open source populares. Pense assim, digamos que seu projeto se chama &ldquo;my-react&rdquo;. Fodeu, no treinamento dele certamente teve muito exemplo de React.js de verdade, e ele vai ficar confundindo as duas coisas o tempo todo. De novo, tenha intuição do que já tem no treinamento pra ajudar o modelo a não ficar confuso.</p>
<p>E você vai ter gasto dezenas de dólares, ou mais, se não tomar cuidado. E no final não vai ter código que funciona direito.</p>
<p>Ah sim, tem gambiarras que dá pra fazer: integrar um RAG com código fonte do que você sabe que ele não tem no treinamento, ou treinar LoRas e usar em conjunto. E mesmo assim não é &ldquo;solução&rdquo;, são tentativas de fazer ele gerar código que faça mais sentido. Mas sozinhos, sem esse tipo de ajuda, neste momento eles ainda não sabem o que fazer.</p>
<p>O que muita gente ignora é que a GRANDE MAIORIA dos principais códigos de verdade do mercado, são todos FECHADOS. A Amazon nunca vai liberar o código do e-commerce deles, nem Alibaba. O iFood ou MercadoLivre idem. Nenhum código de verdade está no modelo. Se estivesse seria um enorme risco de PROCESSO. Eles só podem usar código aberto, de lugares como GitHub ou respostas de Stackoverflow. E lá só tem código BÁSICO E RUIM. Então é só isso que as LLMs conseguem cuspir código BÁSICO E RUIM.</p>
<p>Se a LLM tem conseguido resolver seus problemas, não é porque ele é bom, é porque seu problema é muito simples.</p>
]]></content:encoded><category>llms</category><category>agentes-de-codigo</category><category>zig</category></item></channel></rss>