<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Rust | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/rust/</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/rust/index.xml" rel="self" type="application/rss+xml"/><item><title>Nova linguagem voltada pra IA acabou de ser lançada: Bend 2</title><link>https://akitaonrails.github.io/2026/09/19/nova-linguagem-voltada-pra-ia-bend-2/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/09/19/nova-linguagem-voltada-pra-ia-bend-2/</guid><pubDate>Sat, 19 Sep 2026 12:00:00 -0300</pubDate><description>&lt;p&gt;Anteontem, 17 de setembro, o pesquisador brasileiro Victor Taelin &lt;a href="https://x.com/VictorTaelin/status/2100681226143092875"target="_blank" rel="noopener"&gt;anunciou no X&lt;/a&gt; o lançamento do &lt;strong&gt;Bend 2&lt;/strong&gt;, hospedado em &lt;a href="https://bend-lang.com/"target="_blank" rel="noopener"&gt;bend-lang.com&lt;/a&gt;. O slogan é ambicioso: &lt;em&gt;&amp;ldquo;a fast language that blocks AI mistakes via proof — C speed · CUDA parallelism · Lean proofs · Python syntax&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Uma linguagem que promete rodar quase na velocidade de C, paralelizar sozinha em CPU e GPU, e ainda verificar com prova formal que o código que a IA escreveu faz o que você pediu. É muita promessa junta. Eu nunca tinha encostado no Bend 1, então isso aqui é experiência nova pra mim também: no dia do lançamento, apontei meus agentes pra dois projetos meus em Rust e mandei portar pra Bend 2. O resultado desse teste é a parte mais interessante deste artigo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Anteontem, 17 de setembro, o pesquisador brasileiro Victor Taelin <a href="https://x.com/VictorTaelin/status/2100681226143092875"target="_blank" rel="noopener">anunciou no X</a> o lançamento do <strong>Bend 2</strong>, hospedado em <a href="https://bend-lang.com/"target="_blank" rel="noopener">bend-lang.com</a>. O slogan é ambicioso: <em>&ldquo;a fast language that blocks AI mistakes via proof — C speed · CUDA parallelism · Lean proofs · Python syntax&rdquo;</em>.</p>
<p>Uma linguagem que promete rodar quase na velocidade de C, paralelizar sozinha em CPU e GPU, e ainda verificar com prova formal que o código que a IA escreveu faz o que você pediu. É muita promessa junta. Eu nunca tinha encostado no Bend 1, então isso aqui é experiência nova pra mim também: no dia do lançamento, apontei meus agentes pra dois projetos meus em Rust e mandei portar pra Bend 2. O resultado desse teste é a parte mais interessante deste artigo.</p>
<p>Antes de começar, o disclaimer: isso é uma review feita no <strong>segundo dia de vida</strong> da linguagem, baseada num experimento de um dia. Posso estar errado em várias das minhas assunções, e tá tudo bem. Os comentários estão aí embaixo, sintam-se livres pra corrigir e complementar.</p>
<h2>Quem é Victor Taelin<span class="hx:absolute hx:-mt-20" id="quem-é-victor-taelin"></span>
    <a href="#quem-%c3%a9-victor-taelin" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de tudo, vale apresentar o autor. O <a href="https://github.com/VictorTaelin"target="_blank" rel="noopener">Victor Taelin</a> é um pesquisador brasileiro de programação funcional que passou mais de uma década obcecado por um problema específico: redução ótima de lambda cálculo, o algoritmo do Lamping de 1990, e depois as redes de interação do Lafont.</p>
<p>A <a href="https://gist.github.com/VictorTaelin/77fd5a2a8a4a07e1da6157ebca3c7cf1"target="_blank" rel="noopener">própria autobiografia dele</a> conta a trajetória: trabalhou na Ethereum Foundation, criou a linguagem de provas Formality, aposentou cedo com o ETH que acumulou de salário, voltou ao problema por conta própria, criou a HVM (Higher-Order Virtual Machine) e fundou a Higher Order Company, que levantou <a href="https://x.com/VictorTaelin/status/1743751536465903795"target="_blank" rel="noopener">US$ 4 milhões de seed</a> pra transformar a pesquisa em produto.</p>
<p>E aqui vai um ponto que pra mim importa muito: <strong>eu acho ótimo um brasileiro fazendo trabalho sério de design de linguagem de programação</strong>. A gente quase não tem pesquisa de ponta de verdade nesse país, muito menos em fundamentos de computação. Só por isso, o Taelin já tem o meu respeito e os meus parabéns pelo que construiu até aqui.</p>
<p>Então deixa registrado: as críticas deste artigo são todas construtivas, de alguém que torce pro Bend chegar mais longe, nada contra ele ou contra o projeto.</p>
<h2>De onde vem o Bend<span class="hx:absolute hx:-mt-20" id="de-onde-vem-o-bend"></span>
    <a href="#de-onde-vem-o-bend" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A primeira versão do Bend apareceu em <a href="https://news.ycombinator.com/item?id=40390287"target="_blank" rel="noopener">maio de 2024</a>, com sintaxe parecida com Python rodando em cima da HVM2, e a promessa de paralelismo automático: escreva código sequencial, o runtime distribui em todos os cores e até na GPU, sem thread, sem lock, sem mutex.</p>
<p>O lançamento foi viral. Quase mil pontos no Hacker News, mais de 20 mil estrelas no GitHub, vídeo do Fireship. E aí veio a realidade: a <a href="https://news.ycombinator.com/item?id=40390287"target="_blank" rel="noopener">thread do HN</a> foi impiedosa. Números de 24 bits, sem FFI, soma simples levando 42 minutos na máquina de um usuário, C++ em um core batendo a RTX 4090 do demo. O próprio Taelin admitiu na época que o codegen era ruim, e admitiu agora, na <a href="https://news.ycombinator.com/item?id=49746163"target="_blank" rel="noopener">thread do Bend 2</a>, que o hype de 2024 não converteu em uso: <em>&ldquo;there is also my own failure into making the language actually be used, rather than just a viral moment&rdquo;</em>.</p>
<p>O Bend 2 é a resposta pra isso. E atenção: <strong>é outra linguagem</strong>. O README é explícito que programas de Bend 1 não carregam. Saiu a avaliação por grafos de interação (a HVM); entrou compilação nativa pra um único arquivo C, com clang pra CPU e Metal ou CUDA pra GPU. Os números passam a ser Nat, U32 e F32 de verdade, e o limite de memória pulou de 2 GB pra 8 TB, segundo o <a href="https://gihyo.jp/article/2026/09/bend-2"target="_blank" rel="noopener">anúncio japonês da gihyo</a>. As redes de interação continuam na arquitetura, mas, nas palavras dele, <em>&ldquo;inets live in it architecturally, but they don&rsquo;t exist at runtime&rdquo;</em>.</p>
<h2>O que o Bend 2 promete<span class="hx:absolute hx:-mt-20" id="o-que-o-bend-2-promete"></span>
    <a href="#o-que-o-bend-2-promete" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O pitch novo agora gira em torno de IA. A página do projeto vende uma visão <em>&ldquo;post-AGI&rdquo;</em>: humanos vão parar de escrever e ler código, então precisamos de um jeito sem ambiguidade de dizer pras IAs o que queremos. A solução do Bend é dividir o programa em dois arquivos:</p>
<ul>
<li><code>LAWS.bend</code>: as invariantes do sistema, escritas pelo humano.</li>
<li><code>PROOF.bend</code>: as provas dessas leis, escritas pela IA.</li>
</ul>
<p>O type checker do Bend é um verificador de provas, na linha do Lean e do Rocq, então <code>bend PROOF.bend</code> vira um portão de commit: se a prova não fecha, o código não entra. O demo do site é um joguinho com a lei <em>&ldquo;vencer é impossível&rdquo;</em> e um convite direto: <em>&ldquo;Skeptical? Try breaking the game&rdquo;</em>. O posicionamento é explícito: <strong>LAWS.bend é um AGENTS.md com respaldo matemático</strong>.</p>
<p>E como é a cara disso? A sintaxe lembra Python com tipos dependentes. Um programa completo com efeitos:</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">Base</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Performs effects on the CPU.</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">IO</span><span class="p">(</span><span class="n">Unit</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">  <span class="n">do</span> <span class="n">IO</span><span class="o">&lt;</span><span class="n">Unit</span><span class="o">&gt;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="p">:</span> <span class="n">String</span> <span class="o">&lt;-</span> <span class="n">IO</span><span class="o">.</span><span class="k">try</span><span class="p">(</span><span class="n">String</span><span class="p">,</span> <span class="n">IO</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">&#34;USER&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">IO</span><span class="o">.</span><span class="n">print</span><span class="p">(</span><span class="s2">&#34;Hello, &#34;</span> <span class="o">++</span> <span class="n">name</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>O paralelismo é divide-and-conquer: você quebra o trabalho em dois e o runtime espalha pelos cores que encontrar. E o operador <code>!</code> despacha a função pra GPU:</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"># Computes 2^d in parallel: a tree of d levels, one leaf per unit.</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pow2</span><span class="p">(</span><span class="o">+</span><span class="n">d</span><span class="p">:</span> <span class="n">Nat</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">U32</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">match</span> <span class="n">d</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">0</span><span class="n">n</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">p</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="n">a</span> <span class="n">b</span> <span class="o">=</span> <span class="n">pow2</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="n">pow2</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="n">a</span> <span class="o">+</span> <span class="n">b</span> <span class="p">:</span> <span class="n">U32</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Runs pow2 on the GPU, via `!`.</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">IO</span><span class="p">(</span><span class="n">Unit</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">  <span class="n">result</span> <span class="o">=</span> <span class="n">pow2</span><span class="err">!</span><span class="p">(</span><span class="mi">20</span><span class="n">n</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">IO</span><span class="o">.</span><span class="n">print</span><span class="p">(</span><span class="n">U32</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="n">result</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>Repara nas diferenças pras linguagens mainstream: não existe <code>if</code> (todo branch é um <code>match</code> em <code>True</code>/<code>False</code>), números naturais se casam por padrão de sucessor (<code>1n+p</code>), e o <code>+d</code> marca o parâmetro como <strong>afim</strong>, que é o que permite a linguagem viver sem garbage collector.</p>
<p>E as provas são defs comuns. A lei declara o que vale; a prova é indução com reescritas explícitas:</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"># CLAIM: for every nat x, x + 0 equals x.</span>
</span></span><span class="line"><span class="cl"><span class="n">law</span> <span class="n">add_zero</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="n">x</span><span class="p">:</span> <span class="n">Nat</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">Nat</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="mi">0</span><span class="n">n</span><span class="p">)</span> <span class="o">==</span> <span class="n">x</span> <span class="p">:</span> <span class="n">Nat</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># PROOF: induction on `x`, one rewrite (`%`) per step.</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_zero</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">  <span class="k">match</span> <span class="n">x</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">0</span><span class="n">n</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="o">==</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">xp</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="o">%</span><span class="n">add_zero</span><span class="p">(</span><span class="n">xp</span><span class="p">)</span> <span class="p">:</span> <span class="p">{</span><span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">Nat</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">xp</span><span class="p">,</span> <span class="mi">0</span><span class="n">n</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">_</span> <span class="p">:</span> <span class="n">Nat</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="o">==</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>Sem táticas, sem inferência, tudo anotado à mão. É verbose de propósito: é o preço do checker de 0,1s.</p>
<p>As outras promessas, todas com benchmark na página:</p>
<ul>
<li><strong>Roda rápido</strong>: Game of Life num M4 Max em 7,8s em um core, contra 6,78s do C. Na GPU, 0,06s, ou 124x.</li>
<li><strong>Verifica rápido</strong>: 3.200 instanciações genéricas checadas em 0,38s, contra 19,2s do Lean e 6,04s do Rocq. A ideia é que o agente possa verificar a cada edição.</li>
<li><strong>Paraleliza sozinho</strong>: operador <code>!</code> manda a função pra GPU, sem thread, sem lock, sem kernel escrito à mão.</li>
<li><strong>Sem garbage collector</strong>: tipos afins, cada closure pode ser chamada no máximo uma vez, então a memória se resolve estaticamente.</li>
</ul>
<p>Dois detalhes honestos que eu gostei de ver publicados: o próprio site avisa <em>&ldquo;Bend is still evolving. Expect bugs&rdquo;</em>, e o Taelin declarou que <a href="https://gihyo.jp/article/2026/09/bend-2"target="_blank" rel="noopener">o compilador é 99% escrito por IA</a>, com só o kernel de checagem auditado por humanos. Coerente com a tese dele, pelo menos.</p>
<p>E já existe um plano de negócio: o <a href="https://bend-lang.com/bender"target="_blank" rel="noopener">Bender</a>, um <em>&ldquo;proving agent&rdquo;</em> pago e proprietário, harness em cima dos modelos da Anthropic e da OpenAI hoje, com modelo dedicado e provador simbólico próprio no roadmap.</p>
<h2>Nem todo mundo engoliu os benchmarks<span class="hx:absolute hx:-mt-20" id="nem-todo-mundo-engoliu-os-benchmarks"></span>
    <a href="#nem-todo-mundo-engoliu-os-benchmarks" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A recepção técnica teve ressalvas fortes, e elas importam. O gist do Nezk, <a href="https://gist.github.com/Nezk/dda0511c492cf9bd673885f0341dca0e"target="_blank" rel="noopener">&ldquo;Why the benchmarks of Bend&rsquo;s 2 typechecker are misleading (and possible problems with its type system)&rdquo;</a>, aponta que o Bend pula elaboração, unificação e argumentos implícitos, que é justamente onde Lean e Rocq gastam tempo. Comparar os tempos assim seria comparar maçã com foguete.</p>
<p>Mais pesado: o post do Liam Powell, <a href="https://blog.liampwll.com/posts/bend_vibe_coding/"target="_blank" rel="noopener">&ldquo;Bend 2 and the Vibe-Coding Trap&rdquo;</a>, nota que a expressão <em>&ldquo;formal verification&rdquo;</em> não aparece no site nem no código do Bend, refaz o demo do jogo em SPARK/Ada provando as mesmas duas propriedades automaticamente com GNATprove (<em>&ldquo;all checks proved (12 checks)&rdquo;</em>), sem as 442 linhas de prova manual, e conclui que o Bend está <em>&ldquo;decades behind the state of the art&rdquo;</em>.</p>
<p>São críticas de gente que entende do assunto e merecem resposta. Dito isso, a thread principal do Bend 2 no HN está com <a href="https://news.ycombinator.com/item?id=49746163"target="_blank" rel="noopener">mais de 600 pontos</a>, então atenção o projeto tem.</p>
<h2>O que eu testei<span class="hx:absolute hx:-mt-20" id="o-que-eu-testei"></span>
    <a href="#o-que-eu-testei" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Slogan não compila programa, então eu fui testar. Tenho um repositório <a href="https://github.com/akitaonrails/bend-tests"target="_blank" rel="noopener"><code>bend-tests</code></a> onde apontei meus agentes pra portar projetos reais meus, escritos em Rust, pra Bend 2 (v2.0.16, pinada via mise; pra ter noção da maturidade, a linguagem teve nove releases em dez horas no GitHub, no dia seguinte ao lançamento). A documentação do experimento inteiro está em <code>docs/</code> lá dentro, e é dela que saem as próximas seções.</p>
<h3>Os três candidatos<span class="hx:absolute hx:-mt-20" id="os-três-candidatos"></span>
    <a href="#os-tr%c3%aas-candidatos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Escolhi três projetos meus com perfis diferentes. Já aviso: nenhum dos três é um exemplo digno de Bend, e eu sei disso. Eu só peguei projetos pequenos e simples que eu tinha à mão, meio aleatórios, pra ver onde a linguagem quebra quando esfregam nela código comum do dia a dia.</p>
<table>
  <thead>
      <tr>
          <th>Projeto</th>
          <th>Fit</th>
          <th>Por quê</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ghpending</td>
          <td>ruim</td>
          <td>90% HTTP, JSON e formatação; uma única função pequena que vale provar</td>
      </tr>
      <tr>
          <td>clock-tui</td>
          <td>ruim</td>
          <td>IO de terminal do começo ao fim; a aritmética é U32, impraticável de provar sem mathlib</td>
      </tr>
      <tr>
          <td>ai-jail</td>
          <td>pior</td>
          <td>namespaces, mounts, seccomp, exec: quase tudo syscall</td>
      </tr>
  </tbody>
</table>
<p>O <strong>ghpending</strong> é minha CLI que lista issues e PRs abertos dos meus repositórios via GraphQL. O port cobre o comando principal, os filtros, a ordenação e o config.toml, com saída verificada <strong>byte a byte idêntica</strong> ao binário Rust nos casos testados contra a API real do GitHub (no meu config cheio, a única diferença é a fork view que o port deixou de fora).</p>
<p>O <strong>tclock</strong> é meu fork do clock-tui, relógio de terminal com timer e cronômetro. O port cobre relógio, timer e stopwatch, sem o sistema de widgets (que sozinho é 2.075 linhas do Rust), verificado dirigindo o binário via pty.</p>
<p>O <strong>ai-jail</strong>, meu sandbox pra agentes de IA (bubblewrap, Landlock, seccomp), nem virou código: a avaliação no papel já matou. Um port seria um programa em C com um <code>main</code> em Bend, o que anula o propósito. E olha que ele tinha a parte mais interessante pra provar: a lógica de política, tipo <em>&ldquo;resolução de path nunca escapa da raiz&rdquo;</em>. Mas não tem como embutir isso no binário Rust, então ficou pra uma próxima.</p>
<h3>Os números<span class="hx:absolute hx:-mt-20" id="os-números"></span>
    <a href="#os-n%c3%bameros" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Medi Rust e Bend lado a lado nos dois ports:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>Rust</th>
          <th>Bend</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ghpending, linhas de código</td>
          <td>1.962 (sem testes)</td>
          <td>1.068 Bend + 93 leis/provas + 109 C + 25 JS</td>
      </tr>
      <tr>
          <td>ghpending, dependências</td>
          <td>278 crates no lockfile</td>
          <td>libcurl dinâmico, mais nada</td>
      </tr>
      <tr>
          <td>ghpending, binário</td>
          <td>6,8 MB</td>
          <td>1,4 MB</td>
      </tr>
      <tr>
          <td>ghpending, build limpo</td>
          <td>57s</td>
          <td>62s (0,1s de check; o resto é clang -O3 no C gerado)</td>
      </tr>
      <tr>
          <td>ghpending, run com 43 repos</td>
          <td>3,9s wall / 16ms CPU</td>
          <td>2,1s wall / 28ms CPU</td>
      </tr>
      <tr>
          <td>tclock, binário</td>
          <td>6,7 MB</td>
          <td>115 KB</td>
      </tr>
      <tr>
          <td>tclock, idle 5s</td>
          <td>333ms CPU, 10 MB</td>
          <td>9ms CPU, 6,6 MB</td>
      </tr>
  </tbody>
</table>
<p>Lê essa tabela com cuidado, porque ela engana. As runs são bound de rede. O gap dos 43 repos é o Rust fazendo mais trabalho (fork lookups, query sequencial de viewer). O tclock do Bend redesenha frames inteiros, 26 KB/s contra 2 KB/s de diffs do ratatui. E a contagem menor de linhas é em parte porque o port faz menos. <strong>Nenhum número aqui mostra o Bend mais rápido que o Rust no mesmo trabalho.</strong></p>
<h3>As provas na prática<span class="hx:absolute hx:-mt-20" id="as-provas-na-prática"></span>
    <a href="#as-provas-na-pr%c3%a1tica" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Aqui mora a parte que o Rust não consegue imitar, então vale o detalhe. O design que funcionou foi <strong>provar uma guarda isolada, deixando o algoritmo fora da prova</strong>. O alocador de <code>--limit</code> do ghpending ficou em dois estágios: o primeiro é o algoritmo original em U32, não provado; o segundo é uma guarda em Nat que recebe a proposta do primeiro e corta qualquer excesso. As leis dizem que o orçamento é conservado e que nenhum repo recebe mais itens do que tem.</p>
<p>Essa é a guarda de verdade, do <code>alloc.bend</code> do port:</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"># the lesser of b and w</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">take</span><span class="p">(</span><span class="n">b</span><span class="p">:</span> <span class="n">Nat</span><span class="p">,</span> <span class="n">w</span><span class="p">:</span> <span class="n">Nat</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Nat</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">match</span> <span class="n">b</span> <span class="n">w</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">0</span><span class="n">n</span> <span class="n">w0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="mi">0</span><span class="n">n</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">bp</span> <span class="mi">0</span><span class="n">n</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="mi">0</span><span class="n">n</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">bp</span> <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">wp</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">take</span><span class="p">(</span><span class="n">bp</span><span class="p">,</span> <span class="n">wp</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># what is left of b once w is taken from it</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">rest</span><span class="p">(</span><span class="n">b</span><span class="p">:</span> <span class="n">Nat</span><span class="p">,</span> <span class="n">w</span><span class="p">:</span> <span class="n">Nat</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Nat</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">match</span> <span class="n">b</span> <span class="n">w</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">0</span><span class="n">n</span> <span class="n">w0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="mi">0</span><span class="n">n</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">bp</span> <span class="mi">0</span><span class="n">n</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">bp</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">bp</span> <span class="mi">1</span><span class="n">n</span><span class="o">+</span><span class="n">wp</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="n">rest</span><span class="p">(</span><span class="n">bp</span><span class="p">,</span> <span class="n">wp</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>
<blockquote>
  <p>A lei vale mesmo se o estágio não provado propor lixo. A guarda não confia na aritmética que ela não consegue enxergar. Esse é o padrão que faz prova funcionar em código real.</p>

</blockquote>
<p>O teste de mutação é a medida honesta do negócio. Injetei um off-by-one na guarda que vaza orçamento: as três saídas de exemplo ficaram idênticas, então uma suíte de testes daquele tamanho teria aprovado. O portão de prova rejeitou, com o termo exato que deixou de bater. É o Bend funcionando como prometido, no detalhe.</p>
<p>Agora o preço: 93 linhas de leis e provas pra garantir dois fatos simples sobre uma guarda de umas 30 linhas, começando do zero, porque a Base não traz nenhum lema de Nat (os únicos lemas aritméticos são <code>U32.add_comm</code> e <code>Word.add_comm</code>, que não ajudam em prova de Nat; tivemos que provar associatividade da soma na mão, e o Taelin reconheceu na thread: <em>&ldquo;We need a mathlib!&rdquo;</em>).</p>
<p>E o que ficou de fora da prova: a exatidão do algoritmo (precisaria de lemas de divisão, dias de trabalho), tudo que é U32 (por baixo é um vetor de 32 bits; provar aritmética nele sem biblioteca de lemas é impraticável) e literalmente todo o resto do programa: JSON, ordenação, renderização, IO. A superfície provável de um programa como o ghpending é uns poucos por cento do código.</p>
<p>O checker, por outro lado, é a melhor parte da ferramenta: 0,1s por rodada, erro apontando uma linha exata. Foram umas 30 rodadas de check no ghpending. Pra um agente iterando em loop, é exatamente o formato de feedback que ele precisa.</p>
<h3>O preço de sair pra C<span class="hx:absolute hx:-mt-20" id="o-preço-de-sair-pra-c"></span>
    <a href="#o-pre%c3%a7o-de-sair-pra-c" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Tudo que a Base não tem vira <em>foreign effect</em>: um def em Bend cujo corpo é uma função C (pro binário nativo) e uma gêmea em JS (pro runner de desenvolvimento). Os dois ports precisaram de <strong>oito</strong> efeitos escritos à mão:</p>
<ul>
<li><code>Http.post</code>: porque não existe TLS, HTTP ou DNS (o demo oficial de HTTP hard-coda um IP).</li>
<li><code>Clock.unix</code> e <code>Clock.local</code>: porque <code>IO.now</code> é milissegundos desde o boot, sem hora de parede e sem fuso.</li>
<li><code>Tty.width</code>, <code>Tty.raw</code>, <code>Tty.key</code>, <code>Tty.size</code>: porque não existe stdin, raw mode nem consulta de terminal. Nada disso existe.</li>
<li><code>Clock.millis</code>: conveniência.</li>
</ul>
<p>E o guia oficial é claro sobre o compromisso: <strong>não há promessa de ABI</strong>. Os nomes são internos do runtime e qualquer release pode renomeá-los, então <em>&ldquo;rebuild your effects with every update&rdquo;</em>. Numa linguagem com nove releases em dez horas, boa sorte. Detalhes que completam o quadro: não existe flag pra linkar biblioteca extra (o ghpending usa um wrapper de <code>$CC</code> que injeta <code>-lcurl</code>), tipos de handle customizados são WONTFIX, e incluir header de C no meio do arquivo funcionou por sorte, porque o runtime define nomes curtos tipo <code>lock</code> e <code>u32</code>.</p>
<p>Aqui encaixa o ponto conceitual: <strong>no momento em que você cruza a fronteira do FFI, a prova ficou pra trás</strong>. O verificador garante o que está dentro do modelo dele; o código C do outro lado é ato de fé. O Rust tem exatamente o mesmo problema com <code>unsafe</code>, onde as garantias do borrow checker valem até a borda do bloco e dentro dele a prova é sua. Se você quer garantia forte de ponta a ponta, a fronteira com C precisa ser mínima ou inexistente, e hoje em Bend ela é enorme pra qualquer programa real.</p>
<h3>Onde o Bend encaixa<span class="hx:absolute hx:-mt-20" id="onde-o-bend-encaixa"></span>
    <a href="#onde-o-bend-encaixa" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Cruzando os três projetos com o que a linguagem oferece, o formato que ganha é <strong>um núcleo puro pequeno, com spec afiada que testes cobrem mal, dentro de uma casca fina de efeitos</strong>:</p>
<ul>
<li>alocadores, schedulers, rate limiters, lógica de quota</li>
<li>regras determinísticas de jogo ou simulação, principalmente com rollback netcode (o único domínio onde a Base já tem o necessário: UDP, janela, áudio)</li>
<li>máquinas de estado de consenso, ledger e matching engine</li>
<li>parsers e codecs com lei de round-trip</li>
<li>funções de merge de CRDT (comutativas, associativas, idempotentes)</li>
<li>avaliadores de política de acesso (tipo o do ai-jail)</li>
</ul>
<p>E o centro de design que os autores declaram: <strong>a IA escreve o código e as provas, o humano escreve só o LAWS.bend, e o checker arbitra</strong>. A descrição honesta mais curta que eu achei: uma <em>proof-carrying language for AI-written kernels</em>. Comparado com Lean ou Coq, que provam bem e entregam aplicação mal, o Bend é muito mais executável.</p>
<p>Uma coisa que me surpreendeu nos próprios números: de primeira eu assumiria que o nicho natural era pesquisa acadêmica em matemática. É o contrário. Pra matemática ele perde feio pra Lean 4, Coq e Isabelle: sem táticas, quase sem inferência, sem nada na escala da mathlib, sem reais, e com uma fundação (<code>Type : Type</code>, sem positivity check) que deixaria matemático de cabelo em pé. O que ele faz é <strong>verificação de software</strong>, mais perto de Dafny, F* ou Liquid Haskell do que de assistente de prova.</p>
<p>Duas limitações pesam até nesse nicho: o Bend emite um programa inteiro em vez de uma biblioteca, então o núcleo provado não pode ser linkado numa aplicação Rust hoje; e o custo de prova sem biblioteca de lemas é alto.</p>
<h3>O que mudaria o veredito<span class="hx:absolute hx:-mt-20" id="o-que-mudaria-o-veredito"></span>
    <a href="#o-que-mudaria-o-veredito" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Registrado no <code>use-cases.md</code>, a lista do que me faria reavaliar:</p>
<ul>
<li>saída de biblioteca com ABI C, pra um núcleo provado viver dentro de um programa Rust ou C</li>
<li>U64/I64 e F64</li>
<li>uma biblioteca de lemas pra Nat, listas e ordem</li>
<li>stdin, subprocessos, TLS e JSON na Base, ou um hub de pacotes que tenha isso</li>
<li>builds nativos incrementais ou mais rápidos (62s pra 1.000 linhas hoje)</li>
<li>uma ABI de efeitos que sobreviva a um release</li>
</ul>
<h2>A realidade nua e crua<span class="hx:absolute hx:-mt-20" id="a-realidade-nua-e-crua"></span>
    <a href="#a-realidade-nua-e-crua" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora a minha leitura, e você já sabe que eu não adoço.</p>
<p><strong>É uma linguagem recém-nascida.</strong> Foi lançada anteontem. Fora o repo da própria linguagem, uma busca no GitHub acha hoje só algumas dezenas de spikes de 48 horas, quase todos com zero estrelas, nada que pareça projeto de verdade. As 21 mil estrelas do repo são quase todas herança do Bend 1, o repo foi renomeado. Os demos do site são todos de brinquedo: joguinho, Game of Life, pow2. São demonstrações de possibilidade, ainda sem uso real.</p>
<p><strong>A stdlib precisa crescer uma ordem de magnitude.</strong> A força de uma linguagem mora na biblioteca padrão e no ecossistema. A única exceção histórica a essa regra foi o JavaScript, e aquilo lá é uma lata de vermes que não cabe neste artigo.</p>
<p><strong>Falta a killer app.</strong> Toda linguagem que vingou tinha uma. Os meus testes apontam o nicho (núcleos puros provados, escritos por IA), mas alguém precisa transformar isso num app que o mundo queira usar. E aqui entra uma sugestão concreta, de graça: o Taelin podia escolher software open source popular que ele acha que ficaria melhor em Bend e reescrever. O Taelin é o exato oposto do mantenedor teimoso que torce o nariz pra IA: ele desenhou a linguagem inteira em volta de IA. Então que use isso a favor: pega um ripgrep, um jq, um sqlite da vida, reescreve com IA em Bend e publica a comparação lado a lado. Performance, linhas de código, e principalmente o que as provas garantem que o original não garante. Um before/after assim no mundo real vale mais que mil demos de joguinho. E de brinde, cada rewrite desses devolve o feedback mais valioso que existe: quais buracos da stdlib doeram de verdade num projeto real. Nada prioriza melhor o que implementar na próxima versão do que código de verdade esbarrando no que falta.</p>
<p><strong>Não espere webapp, mobile ou qualquer coisa do dia a dia tão cedo.</strong> Talvez nem seja o propósito, e tudo bem também. Mas a promessa de <em>&ldquo;velocidade de C&rdquo;</em> corta pra outro lado: se o diferencial é performance, é mais fácil ir direto pro C, ainda mais hoje, com LLM escrevendo o código por você.</p>
<p>E aqui entra a minha situação concreta. Eu uso Rust pra caramba. <strong>Odeio</strong> a sintaxe, acho a ergonomia sofrível. Mas como quem escreve o código é o LLM e eu não preciso lidar com aquilo na mão, eu tolero, e em troca levo performance, garantias boas o suficiente e um ecossistema gigantesco de crates e frameworks. Ecossistema. É isso que decide.</p>
<blockquote>
  <p>Eu não escolho linguagem pela elegância do paper. Escolho pelo tamanho do ecossistema que eu não preciso escrever na mão.</p>

</blockquote>
<h2>O foco em prova e GPU, e a dúvida que fica<span class="hx:absolute hx:-mt-20" id="o-foco-em-prova-e-gpu-e-a-dúvida-que-fica"></span>
    <a href="#o-foco-em-prova-e-gpu-e-a-d%c3%bavida-que-fica" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Uma coisa que me incomodou de leve o tempo inteiro: o Taelin claramente concentra tudo em dois pilares, provas e GPU, e eu não tinha certeza do que isso significava pro escopo da linguagem. Fui ler o que ele mesmo disse no <a href="https://x.com/VictorTaelin/status/2100374221671051472"target="_blank" rel="noopener">thread de pré-lançamento</a> e no HN, e a história é mais interessante do que eu assumi.</p>
<p>O pilar GPU é a herança de pesquisa. São dez anos de interaction nets e redução ótima, da HVM até aqui. Mas tem uma nuance que me surpreendeu: o Bend 2 conscientemente <strong>trai</strong> o idealismo acadêmico dele. Nas palavras do próprio: <em>&ldquo;Bend2 is designed to be practical, not idealistic&rdquo;</em>, mais perto de C do que de Haskell, porque ele não conseguiu fazer as interaction nets baterem as variantes lower-order em hardware comum. A GPU ficou como aplicação prática da obsessão, sem virar fetiche.</p>
<p>O pilar prova é mais velho que a era LLM: vem da época da Ethereum Foundation, de contrato imutável segurando milhões com bug, que gerou o Formality. A era LLM só deu pra obsessão antiga uma roupagem de mercado: <em>&ldquo;the same capabilities that proved Navier-Stokes will now be writing real proofs that your own apps are correct&rdquo;</em>.</p>
<p>E aí vem a minha hipótese que não sobreviveu à pesquisa. Eu estava pronto pra escrever que a stdlib mínima talvez fosse <strong>de propósito</strong>, que a ideia nunca foi programação geral, e que a minha crítica ao deserto de bibliotecas estaria mirando o alvo errado. Errado eu. As palavras dele são de ambição geral mesmo: <em>&ldquo;the potential to become the most sensible choice for any vibe-coded project&rdquo;</em>, <em>&ldquo;Bend is made for the general public&rdquo;</em>, e o time <em>&ldquo;working hard to [&hellip;] make this language competitive with the mature alternatives in the market&rdquo;</em>. A única magreza deliberada é a do checker, sem inferência, verbose de propósito. A Base pequena é dia dois de vida mesmo, tanto que ele próprio pede uma mathlib.</p>
<p>Isso faz a minha crítica à stdlib morder ainda <strong>mais</strong> forte: se o objetivo declarado é ser a escolha pra qualquer projeto vibe-coded, a comparação com ecossistemas maduros é o jogo que ele mesmo escolheu jogar.</p>
<p>E tem o ângulo do negócio, que ele admite com uma franqueza rara: <em>&ldquo;we ship without a product&rdquo;</em>. A linguagem é o substrato gratuito; o vendável é o <a href="https://bend-lang.com/bender"target="_blank" rel="noopener">Bender</a> e a infraestrutura de prova que vem atrás. Vale ficar de olho nesse espaço.</p>
<p>A dúvida que sobra, e essa eu registro sem resposta: a ambição generalista é o norte dele, mas a realidade que eu medi empurra o Bend pro nicho de núcleos verificados escritos por IA. Qual dos dois vence, só o tempo diz.</p>
<h2>O recado final<span class="hx:absolute hx:-mt-20" id="o-recado-final"></span>
    <a href="#o-recado-final" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se eu pudesse dar um conselho pro Taelin, seria esse: a parte difícil, a pesquisa, ele já fez. Agora vem a parte menos glamourosa, que é a que decide se linguagem vive ou morre: <strong>marketing e construção de ecossistema</strong>. Adoção, material de ensino, killer app, stdlib, mathlib. Ir muito além de toys e demos.</p>
<p>O exemplo que me vem à cabeça é o Zig. Anos de estrada e ainda briga por adoção, mesmo tendo uso prático imediato: o <code>zig cc</code> sozinho já é um substituto trivial pra compilar C, sem usar nada da linguagem. Tem app real usando, o <a href="https://ghostty.org/"target="_blank" rel="noopener">Ghostty</a> do Mitchell Hashimoto é o caso famoso. Mas o Andrew Kelley, o autor, é uma figura notoriamente difícil, da turma que torce o nariz pra IA, e claramente não entende de construção de ecossistema, e isso segura o Zig pra trás. Perfeccionismo técnico não combina com marketing e adoção.</p>
<p>O Bend 2 tem potencial de verdade. Checker instantâneo, provas pegando bug que teste não pega, paralelismo sem cerimônia. Mas potencial sozinho não significa nada se a adoção não vier. Eu torço pra vir, e torço duplo por ser um brasileiro liderando.</p>
<p>E uma provocação final, dessa vez pra você que leu até aqui. Stdlib rala, zero frameworks, zero bibliotecas de terceiros, nenhuma killer app: tudo isso deixa de ser problema quando você olha de outro ângulo e vira mapa de oportunidade. Cada coisa que falta no Bend é uma coisa que alguém vai ter que escrever, e quem chegar primeiro grava o nome na história do projeto. Se você sempre quis contribuir com open source de impacto mas nunca achou espaço nos projetos maduros, onde já existe de tudo, aqui tem um terreno vazio esperando.</p>
<p>São os meus dois centavos. Não levem tão a sério.</p>
]]></content:encoded><category>linguagens-de-programacao</category><category>inteligencia-artificial</category><category>rust</category></item><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>Brincando de TUI com LLMs - Ratatui+Bubbletea</title><link>https://akitaonrails.github.io/2026/06/08/brincando-tui-llms-ratatui-bubbletea/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/06/08/brincando-tui-llms-ratatui-bubbletea/</guid><pubDate>Mon, 08 Jun 2026 16:00:00 -0300</pubDate><description>&lt;p&gt;Antes de falar do brinquedo novo, um lembrete rápido: &lt;a href="https://github.com/akitaonrails/ai-jail"target="_blank" rel="noopener"&gt;ai-jail&lt;/a&gt; e &lt;a href="https://github.com/akitaonrails/ai-memory"target="_blank" rel="noopener"&gt;ai-memory&lt;/a&gt; continuam recebendo bastante atenção. Issues, pull requests, pequenas correções, melhorias de empacotamento, bugs de ambiente, ajustes de documentação. Aquelas coisas que ninguém vê num post de lançamento, mas que transformam experimento em ferramenta que dá pra usar todo dia.&lt;/p&gt;
&lt;p&gt;Eu gosto dessa fase. O projeto deixa de ser &amp;ldquo;o que eu imaginei no meu setup&amp;rdquo; e vira &amp;ldquo;o que quebra na máquina de outra pessoa&amp;rdquo;. Aí aparece Windows, macOS, Arch, AUR, path esquisito, token salvo em lugar diferente, shell diferente, terminal diferente. Chato? Sim. Necessário? Também.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Antes de falar do brinquedo novo, um lembrete rápido: <a href="https://github.com/akitaonrails/ai-jail"target="_blank" rel="noopener">ai-jail</a> e <a href="https://github.com/akitaonrails/ai-memory"target="_blank" rel="noopener">ai-memory</a> continuam recebendo bastante atenção. Issues, pull requests, pequenas correções, melhorias de empacotamento, bugs de ambiente, ajustes de documentação. Aquelas coisas que ninguém vê num post de lançamento, mas que transformam experimento em ferramenta que dá pra usar todo dia.</p>
<p>Eu gosto dessa fase. O projeto deixa de ser &ldquo;o que eu imaginei no meu setup&rdquo; e vira &ldquo;o que quebra na máquina de outra pessoa&rdquo;. Aí aparece Windows, macOS, Arch, AUR, path esquisito, token salvo em lugar diferente, shell diferente, terminal diferente. Chato? Sim. Necessário? Também.</p>
<p>Mas o prato principal de hoje não é ai-jail nem ai-memory. É TUI. Mais especificamente, é minha mania recente de escrever pequenas ferramentas de terminal em Rust com <a href="https://ratatui.rs/"target="_blank" rel="noopener">Ratatui</a>, mas querendo que elas fiquem com cara de <a href="https://github.com/charmbracelet/bubbletea"target="_blank" rel="noopener">Bubble Tea</a>.</p>
<h2>O app que não vou abrir<span class="hx:absolute hx:-mt-20" id="o-app-que-não-vou-abrir"></span>
    <a href="#o-app-que-n%c3%a3o-vou-abrir" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Nas últimas semanas também mexi num projeto privado que não pretendo abrir. É uma TUI pra acompanhar minha conta nos EUA, investimentos, assets, posições, covered calls, open calls, essas coisas chatas de adulto. Não vou publicar o código porque é o tipo de ferramenta que nasceu colada no meu fluxo financeiro, nas minhas planilhas, nos meus extratos e nas minhas gambiarras internas. Abrir isso seria transformar uma ferramenta minha em suporte gratuito pra bug de banco dos outros. Tô fora.</p>
<p>Mas dá pra mostrar a cara geral. Censurei o que precisava censurar:</p>
<p><strong>Observação óbvia, mas vou deixar explícito: todos os valores, tickers, posições e datas mostrados no screenshot são dados falsos de demonstração. Não é dado real da minha conta, nem portfolio real, nem recomendação de investimento.</strong></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/06/08/ratatui-bubbletea/private-portfolio-censored.png" alt="TUI privada de portfolio em terminal, com abas de dashboard e tabela de holdings. O nome do banco foi censurado."  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/06/08/ratatui-bubbletea/private-allocation-censored.png" alt="TUI privada mostrando uma visão de alocação por classe de asset, com nomes sensíveis censurados."  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/06/08/ratatui-bubbletea/private-open-calls-censored.png" alt="TUI privada mostrando uma aba de open calls com dados falsos de demonstração e nomes sensíveis censurados."  loading="lazy" /></p>
<p>Toda vez que faço uma TUI pequena, a mesma dúvida aparece: que stack usar?</p>
<p>Se eu fosse escrever tudo na unha, provavelmente escolheria Python ou Go. São linguagens mais confortáveis pra esse tipo de utilitário pequeno. Python porque é direto, cheio de biblioteca, ótimo pra script que conversa com API e parseia JSON. Go porque gera binário simples, cruza plataforma bem, e tem ergonomia boa pra CLI.</p>
<p>Só que hoje eu raramente escrevo esse código todo na unha. Eu orquestro Claude, Codex, OpenCode, mando revisar, mando refatorar, mando testar. Nesse cenário, a falta de ergonomia do Rust pesa menos, porque quem briga com borrow checker, trait bound e lifetime chato é o LLM. Eu fico mais no papel de revisor e usuário final. E aí Rust começa a ficar muito atraente.</p>
<p>Ferramental bom. <code>cargo</code> é ótimo. Testes são fáceis de rodar. Cross compile é administrável. O binário final é limpo. Erro de memória idiota praticamente some. Não estou dizendo que Rust é magicamente melhor que Go ou Python pra toda TUI. Não é. Mas com LLM escrevendo a maior parte, o custo que normalmente me faria evitar Rust cai bastante. E os modelos atuais estão surpreendentemente competentes em Rust, desde que você mande rodar teste, consertar warning e não aceite a primeira versão como se fosse revelação divina.</p>
<h2>Ratatui é bom, mas eu queria a cara do Charm<span class="hx:absolute hx:-mt-20" id="ratatui-é-bom-mas-eu-queria-a-cara-do-charm"></span>
    <a href="#ratatui-%c3%a9-bom-mas-eu-queria-a-cara-do-charm" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://ratatui.rs/"target="_blank" rel="noopener">Ratatui</a> é uma biblioteca Rust pra construir interfaces ricas no terminal: dashboards, apps interativos, tabelas, listas, gauges, layouts, tudo aquilo que antigamente a gente chamava de &ldquo;ncurses moderno&rdquo;. Ele é o sucessor espiritual do <code>tui-rs</code>, e hoje é provavelmente o caminho mais óbvio se você quer fazer TUI séria em Rust.</p>
<p>Ratatui funciona bem. O problema é gosto pessoal: por padrão, não acho bonito. Não é feio no sentido &ldquo;isso parece programa de DOS de 1993&rdquo;. Dá pra fazer coisa boa. Mas muita TUI em Ratatui cai naquela estética quadrada, cinza, funcional, meio admin panel de datacenter.</p>
<p>Eu gosto mais da pegada da <a href="https://charm.sh/"target="_blank" rel="noopener">Charmbracelet</a>. O <code>gum</code>, por exemplo, é uma coleção de comandos pra deixar shell script interativo sem virar festival de <code>read -p</code> e <code>case</code>. Prompt, select, filter, spinner, tabela, pager, estilo. Você monta um script bash com cara de app decente em minutos.</p>
<p>E tem o <a href="https://github.com/charmbracelet/bubbletea"target="_blank" rel="noopener">Bubble Tea</a>, em Go, inspirado na arquitetura Elm. Você tem um model, recebe mensagens, atualiza estado, renderiza uma view. É uma ideia simples e muito boa pra terminal: estado entra, evento entra, view sai. A Charm também tem um gosto visual que eu curto: cores boas, espaçamento decente, componentes leves, nada com cara de formulário WinForms jogado no terminal.</p>
<p>Então eu queria o seguinte: continuar em Rust com Ratatui, mas roubar a sensação de Bubble Tea. Não o runtime inteiro. Não um clone perfeito. Só a direção visual e alguns componentes.</p>
<p>Daí nasceu o <a href="https://github.com/akitaonrails/ratatui-bubbletea"target="_blank" rel="noopener">ratatui-bubbletea</a>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/06/08/ratatui-bubbletea/ratatui-bubbletea-example.png" alt="Showcase do ratatui-bubbletea rodando em terminal, com SelectList, Spinner, Theme, ThemedTable, TextInput, Progress e Viewport no estilo Charm."  loading="lazy" /></p>
<p>O projeto não substitui o Ratatui. Ele fica por cima. A ideia é oferecer uma camada de tema e componentes com inspiração em Bubble Tea/Bubbles, mas ainda renderizando com Ratatui.</p>
<p>Hoje ele é dividido em crates:</p>
<ul>
<li><code>ratatui-bubbletea-theme</code>, com paleta, helpers e estilos compartilhados.</li>
<li><code>ratatui-bubbletea-components</code>, com componentes prontos como select list, spinner, progress, table, text input, viewport e help.</li>
<li><code>ratatui-tea</code>, opcional, pra quem quer uma ergonomia mais perto de <code>Model</code>, <code>Msg</code> e <code>Cmd</code>, sem fingir que Rust virou Go.</li>
</ul>
<p>O README tem os exemplos completos e a documentação do que já existe. Por enquanto, eu trato como toolkit pequeno. Serve pra meus apps, e se servir pra mais alguém, ótimo. Se não servir, também não tem drama. O objetivo era parar de reimplementar o mesmo tema e os mesmos widgets toda vez que eu começava uma TUI nova.</p>
<h2>ai-usagebar como exemplo público<span class="hx:absolute hx:-mt-20" id="ai-usagebar-como-exemplo-público"></span>
    <a href="#ai-usagebar-como-exemplo-p%c3%bablico" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Como não vou abrir o app financeiro, precisava de um exemplo público usando essa estética. Então atualizei meu <a href="https://github.com/akitaonrails/ai-usagebar"target="_blank" rel="noopener">ai-usagebar</a>.</p>
<p>Pra quem não lembra, expliquei o projeto no post <a href="/2026/05/24/criei-widget-waybar-omarchy-monitorar-uso-llms-ai-usagebar/">&ldquo;Criei um Widget de Waybar pra Omarchy pra Monitorar Uso de Planos de LLM: ai-usagebar&rdquo;</a>. Ele nasceu como widget de Waybar pra mostrar uso de planos de LLM: Claude, OpenAI Codex/ChatGPT, Z.AI, OpenRouter. Bateu o olho na barra, sabe se está perto do limite e qual vendor usar agora.</p>
<p>Depois virou também TUI standalone. E graças a contribuições, hoje suporta Windows também. Isso é engraçado porque a origem dele é totalmente Linux/Omarchy/Waybar. Mas é open source: alguém usa em outro ambiente, encontra atrito, manda PR, e pronto, o projeto fica menos provinciano.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/06/08/ratatui-bubbletea/ai-usagebar.png" alt="ai-usagebar em modo TUI mostrando dashboard de uso de OpenAI, abas de vendors, gauges de limite e atalhos no rodapé."  loading="lazy" /></p>
<p>No ai-usagebar a diferença visual não muda a vida de ninguém. É uma tela simples. Abas, gauges, texto, atalhos. Mas é um bom exemplo porque é pequeno, real, testado, empacotado, e não depende do meu app privado. Dá pra olhar o código e ver como apliquei a camada de tema.</p>
<h2>Os apps de coceira própria<span class="hx:absolute hx:-mt-20" id="os-apps-de-coceira-própria"></span>
    <a href="#os-apps-de-coceira-pr%c3%b3pria" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esses projetos todos seguem a mesma lógica: eu tenho uma coceira, faço uma ferramenta pequena, uso, ajusto, empacoto. Nada de roadmap de startup. Nada de manifesto. Só ferramenta que resolve um incômodo meu.</p>
<p>Um exemplo recente é o <a href="https://github.com/akitaonrails/clock-tui"target="_blank" rel="noopener">clock-tui</a>. Ele não nasceu comigo. Era um projeto Rust antigo, <code>tclock</code>, que parecia abandonado. Eu queria um relógio grande no meu segundo monitor vertical. Aquele monitor fica do lado, e eu gosto de ter um &ldquo;desk clock&rdquo; enorme ocupando parte da tela. Usei o clock-tui original por um tempo.</p>
<p>Só que uma tela vertical inteira só pra mostrar hora é desperdício. Eu já tinha o <a href="https://github.com/akitaonrails/ghpending"target="_blank" rel="noopener">ghpending</a>, que lista issues e PRs abertos em todos os meus projetos. Ele é meu &ldquo;tem alguém esperando resposta?&rdquo; em formato de CLI. Também queria ver minha agenda.</p>
<p>Então fiz outro app pequeno: <a href="https://github.com/akitaonrails/google-calendar-tui"target="_blank" rel="noopener">google-calendar-tui</a>. Ele usa as contas Gmail já registradas no GNOME Online Accounts e resume meus próximos compromissos no terminal. Read-only. Sem ritual de OAuth novo. Sem mais um config secreto espalhado em lugar esquisito.</p>
<p>Com essas duas peças, modernizei o clock-tui e adicionei layout de widgets auto-refresh. Agora o relógio ocupa a tela como eu queria, mas em volta dele aparecem pendências do GitHub e agenda do Google Calendar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/06/08/ratatui-bubbletea/tclock.png" alt="tclock em monitor vertical, com relógio grande, widget de pendências do GitHub à esquerda e agenda do Google Calendar à direita."  loading="lazy" /></p>
<p>Isso é exatamente o tipo de coisa que eu nunca faria se custasse um fim de semana inteiro. Mas com LLM, vira uma sequência de tarefas pequenas: lê o projeto antigo, moderniza dependências, roda teste, adiciona módulo de widget, chama ghpending, chama google-calendar-tui, organiza layout, corrige bug, empacota. Ainda dá trabalho. Mas é trabalho de supervisão e refinamento, não de ficar brigando com boilerplate.</p>
<h2>Ajustando Geary sem fork<span class="hx:absolute hx:-mt-20" id="ajustando-geary-sem-fork"></span>
    <a href="#ajustando-geary-sem-fork" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Outra coceira veio do GNOME.</p>
<p>Como eu queria centralizar minhas contas no GNOME Online Accounts, comecei a usar <a href="https://wiki.gnome.org/Apps/Geary"target="_blank" rel="noopener">Geary</a> e o calendário do GNOME no segundo monitor também. Gosto da ideia: contas configuradas uma vez, apps do desktop reaproveitando a mesma infraestrutura, nada de cada programinha inventar seu próprio login.</p>
<p>O problema é que o Geary tem um layout fixo de três colunas. Numa tela normal, tudo bem. Num monitor vertical, fica apertado. A coluna de pastas, a lista de mensagens e o corpo do e-mail brigam por espaço. Eu não queria manter um fork do Geary só pra esconder uma sidebar. Fork de app GTK grande é dívida. Você pisca e passa o resto da vida resolvendo conflito de merge.</p>
<p>Então fiz um módulo GTK: <a href="https://github.com/akitaonrails/geary-hide-sidebar-module"target="_blank" rel="noopener">geary-hide-sidebar-module</a>. Ele injeta um comportamento no Geary em runtime e permite recolher/expandir a sidebar, com atalho e auto-collapse em janela estreita. Sem recompilar Geary. Sem manter fork. Só um módulo pequeno com uma responsabilidade bem específica.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/06/08/ratatui-bubbletea/geary-sidebar-hidden.png" alt="Geary com sidebar recolhida, mostrando lista de e-mails e corpo da mensagem em layout mais confortável para tela vertical."  loading="lazy" /></p>
<p>Essa é uma lição boa: às vezes você não precisa forkar a aplicação inteira. Dá pra ajustar por fora. GTK_MODULES existe. Wrapper existe. Config existe. CSS existe. Script existe. Nem toda personalização precisa virar uma manutenção eterna de código dos outros.</p>
<h2>Código pequeno ficou barato<span class="hx:absolute hx:-mt-20" id="código-pequeno-ficou-barato"></span>
    <a href="#c%c3%b3digo-pequeno-ficou-barato" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>É por isso que estou insistindo tanto nessa ideia de apps pequenos. Com LLMs atuais, ficou muito fácil implementar uma ideia específica pra deixar seu ambiente do jeito que você quer. Sem esperar alguém ter exatamente a mesma dor. Sem ficar procurando no GitHub por três horas uma ferramenta que faz 80% do que você quer e 40% do que você não quer.</p>
<p>Você descreve a coceira, cria um repo, pede um protótipo, testa, manda refatorar, manda escrever teste, manda empacotar. Se ficar ruim, joga fora. Se ficar bom, instala e usa.</p>
<p>Eu não deixo isso virar gambiarra jogada no <code>~/bin</code> sem dono. Meus apps têm testes. Têm builds automatizados. Muitos têm release no GitHub, pacote no AUR, instalação normal com <code>yay</code> no Arch. Usam paths XDG, então config vai pra <code>~/.config</code>, cache vai pra <code>~/.cache</code>, dados vão pra <code>~/.local/share</code>. Fica organizado. Dá pra atualizar. Dá pra remover. Dá pra outra pessoa clonar e contribuir.</p>
<p>Pra fechar, os links todos num lugar só:</p>
<ul>
<li><a href="https://github.com/akitaonrails/ratatui-bubbletea"target="_blank" rel="noopener">ratatui-bubbletea</a></li>
<li><a href="https://github.com/akitaonrails/ai-usagebar"target="_blank" rel="noopener">ai-usagebar</a></li>
<li><a href="https://github.com/akitaonrails/clock-tui"target="_blank" rel="noopener">clock-tui</a></li>
<li><a href="https://github.com/akitaonrails/ghpending"target="_blank" rel="noopener">ghpending</a></li>
<li><a href="https://github.com/akitaonrails/google-calendar-tui"target="_blank" rel="noopener">google-calendar-tui</a></li>
<li><a href="https://github.com/akitaonrails/geary-hide-sidebar-module"target="_blank" rel="noopener">geary-hide-sidebar-module</a></li>
<li><a href="https://github.com/akitaonrails/ai-jail"target="_blank" rel="noopener">ai-jail</a></li>
<li><a href="https://github.com/akitaonrails/ai-memory"target="_blank" rel="noopener">ai-memory</a></li>
</ul>
<p>Se você usar algum deles e quebrar no seu setup, abre issue. Se souber corrigir, manda PR. Eu uso essas ferramentas de verdade, então bug report útil tende a virar correção rápido.</p>
<p>Minha regra prática é essa: personalize seu ambiente sem transformar tudo em gambiarra. Faça ferramenta pequena. Teste. Empacote. Documente. Deixe o LLM carregar o peso chato e use seu cérebro pra decidir o que presta.</p>
<p>Essa é a parte divertida de programar que tinha sumido um pouco. Agora voltou.</p>
]]></content:encoded><category>ai-usagebar</category><category>ferramentas-de-desenvolvimento</category><category>rust</category><category>open-source</category></item><item><title>Criei um Sistema de Memória pra Agentes de Código: ai-memory</title><link>https://akitaonrails.github.io/2026/05/23/criei-sistema-memoria-agentes-codigo-ai-memory/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/05/23/criei-sistema-memoria-agentes-codigo-ai-memory/</guid><pubDate>Sat, 23 May 2026 16:30:00 -0300</pubDate><description>&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/23/ai-memory/ai-memory-logo.png" alt="Logo do projeto ai-memory: pictograma circular com um chip estilizado abrindo “asas” tipo bibliotecas/páginas pra cada lado, ao lado do texto ai-memory em itálico." loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Há cinco dias eu &lt;a href="https://akitaonrails.github.io/2026/05/18/memoria-agentes-karpathy-llm-wiki-agentmemory/"&gt;escrevi um post longo sobre memória de agentes de código&lt;/a&gt; onde recomendei o &lt;a href="https://github.com/rohitg00/agentmemory"target="_blank" rel="noopener"&gt;agentmemory&lt;/a&gt; como solução. Depois de uma semana rodando ele em produção pessoal, voltei atrás. Esse post explica o que deu errado, e o projeto open source que comecei a construir pra resolver: o &lt;a href="https://github.com/akitaonrails/ai-memory"target="_blank" rel="noopener"&gt;ai-memory&lt;/a&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/23/ai-memory/ai-memory-logo.png" alt="Logo do projeto ai-memory: pictograma circular com um chip estilizado abrindo “asas” tipo bibliotecas/páginas pra cada lado, ao lado do texto ai-memory em itálico."  loading="lazy" /></p>
<p>Há cinco dias eu <a href="/2026/05/18/memoria-agentes-karpathy-llm-wiki-agentmemory/">escrevi um post longo sobre memória de agentes de código</a> onde recomendei o <a href="https://github.com/rohitg00/agentmemory"target="_blank" rel="noopener">agentmemory</a> como solução. Depois de uma semana rodando ele em produção pessoal, voltei atrás. Esse post explica o que deu errado, e o projeto open source que comecei a construir pra resolver: o <a href="https://github.com/akitaonrails/ai-memory"target="_blank" rel="noopener">ai-memory</a>.</p>
<h2>Por que o agentmemory não rolou<span class="hx:absolute hx:-mt-20" id="por-que-o-agentmemory-não-rolou"></span>
    <a href="#por-que-o-agentmemory-n%c3%a3o-rolou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O agentmemory tem as ideias certas (LLM Wiki do Karpathy, consolidação em tiers, hooks de captura automática, MCP) mas a implementação tem problemas estruturais que doem em uso diário. Os principais que peguei na prática:</p>
<ul>
<li><strong>Reindexação do BM25 a cada restart</strong>. O índice persiste num arquivo (<code>mem:index:bm25.bin</code>) através de um KV remoto chamado iii-engine. Com poucas observações isso voa, mas passa de 10K observações e o <code>state::set</code> começa a dar timeout, o arquivo fica em ~96 bytes vazio, e cada restart paga ~5 minutos reconstruindo do zero (<a href="https://github.com/rohitg00/agentmemory/issues/309"target="_blank" rel="noopener">issue #309</a>, aberta).</li>
<li><strong>Janela de perda de dados de 5 segundos</strong>. Toda persistência passa por um debounce de 5s do <code>IndexPersistence</code>. Quando o timeout de 30s do <code>state::set</code> estoura, o processo Node morre levando junto tudo que tava em memória (<a href="https://github.com/rohitg00/agentmemory/issues/204"target="_blank" rel="noopener">#204</a>).</li>
<li><strong>Dois caminhos de leitura de config no mesmo código</strong>. <code>process.env</code> direto num lugar, <code>getMergedEnv()</code> em outro. O famoso &ldquo;setei a variável e nada mudou&rdquo; (<a href="https://github.com/rohitg00/agentmemory/issues/456"target="_blank" rel="noopener">#456</a>).</li>
<li><strong>Hook errado em ~47% das tool calls do Claude Code</strong>. O hook leu <code>data.tool_output</code>, mas o Claude Code manda <code>tool_response</code>. Seis semanas com observações sumindo silenciosamente (<a href="https://github.com/rohitg00/agentmemory/issues/539"target="_blank" rel="noopener">#539</a>).</li>
<li><strong>Engine roda no diretório de trabalho do chamador</strong>. Usuários de Windows acharam que perderam memórias porque cada terminal abria o state store num path diferente (<a href="https://github.com/rohitg00/agentmemory/issues/303"target="_blank" rel="noopener">#303</a>, aberta).</li>
</ul>
<p>Tem mais. Documentei <a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/issues-agentmemory.md"target="_blank" rel="noopener">aqui</a> no meu repo de pesquisa. O autor é responsivo, o repo tem 15.7K estrelas, e mesmo assim a arquitetura escolhida (TypeScript MCP + iii-engine Rust separado + 4 portas, 3 processos, índices em memória persistidos via KV remoto) acumula uma classe de bug que não tem como fechar sem reescrever metade do sistema. A forma do problema é estrutural, não falta de esforço do mantenedor.</p>
<p>Foi a mesma história com toda outra solução que testei: ou é incompleta, ou focada demais em alguma tecnologia específica (vector DB proprietário, knowledge graph que precisa de Neo4j, pipeline LLM elaborado), ou exige que o usuário grite <code>write_note</code> toda vez que quer salvar algo. Nenhuma &ldquo;sai do caminho&rdquo; o bastante pra eu confiar em sessões longas.</p>
<h2>O que eu queria desde o começo<span class="hx:absolute hx:-mt-20" id="o-que-eu-queria-desde-o-começo"></span>
    <a href="#o-que-eu-queria-desde-o-come%c3%a7o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando comecei minha <a href="/2026/05/14/terminando-maratona-ia-sucesso-ou-fracasso/">maratona de 37 dias de vibe coding</a> lá em fevereiro, sabia que precisava de algum organizador de conhecimento. Levei mais de 500 horas de experimentação pra cristalizar o que eu queria de verdade.</p>
<p>Pra quem usa só um dos três (Claude Code, ou Codex, ou opencode) e fica nele, você provavelmente não precisa de nada extra. Os três já fazem um trabalho razoável de gerenciar memória dentro da sessão. Claude Code tem três níveis de compaction (microcompact por gap temporal, autocompact por threshold de token, sessionMemoryCompact experimental). Codex tem auto_compact_token_limit por modelo. opencode tem compaction ancorada com buffer de 20K. Cobri isso em detalhe <a href="/2026/05/18/memoria-agentes-karpathy-llm-wiki-agentmemory/">no post anterior</a>.</p>
<p>A única disciplina extra que vale a pena adotar nesse caso é manter um diretório <code>docs/</code> no projeto com arquivos markdown organizados por tópico, e pedir pro agente salvar lá quando algo importante acontece. Pesquisas que não devem ser refeitas, pegadinhas conhecidas, justificativa de por que escolheu opção A em vez de B. Funciona. Exige disciplina.</p>
<p>O problema aparece pra quem é como eu, que pula entre agentes no mesmo projeto. Eu uso Claude Code como padrão (acho o harness, o orquestrador de subagentes, e o planning de tarefas longas superior). Mas quando o Claude empaca num problema, eu disparo o Codex no mesmo diretório só pra &ldquo;agitar&rdquo;. O Codex pensa mais &ldquo;fora da caixa&rdquo; e às vezes encontra uma solução que o Claude não enxergou. Aí volto pro Claude pra implementar com mais cuidado.</p>
<p>Esse vai-e-volta entre agentes é o cenário onde a memória externa importa muito. Sem ela:</p>
<ul>
<li>Você fecha o Claude Code, abre o Codex no mesmo diretório, e o Codex não sabe nada do que foi discutido nas últimas 4 horas.</li>
<li>Você precisa pedir manualmente pro Claude escrever uma nota de handoff em <code>./HANDOFF.md</code>, depois pedir pro Codex ler. Toda vez.</li>
<li>Projetos que você deixou parados por semanas: você abre, esqueceu por que decidiu não usar Redis, esqueceu qual era o bug que tinha na biblioteca X.</li>
</ul>
<p>Karpathy resolveu o conceito da coisa no <a href="https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f"target="_blank" rel="noopener">LLM Wiki dele</a>, que detalhei no post anterior. Mas tem uma frase específica do gist que ficou na minha cabeça:</p>
<blockquote>
  <p>O <code>index.md</code> é &ldquo;surpreendentemente bom — em ~100 fontes e algumas centenas de páginas, sem precisar de embeddings&rdquo;.</p>

</blockquote>
<p>Aquela linha resume o que eu queria. <strong>Não precisa de tecnologia complicada.</strong> Storage simples (markdown em git), índice simples (SQLite com FTS5 e links), instalação simples (um binário ou container), uso automático (hooks que disparam sozinhos), zero manutenção (memória decai sem o usuário mexer).</p>
<p>Foi por isso que comecei o ai-memory.</p>
<h2>A pesquisa antes do código<span class="hx:absolute hx:-mt-20" id="a-pesquisa-antes-do-código"></span>
    <a href="#a-pesquisa-antes-do-c%c3%b3digo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Em vez de só sair codando, pesquisamos o código-fonte e issues dos projetos relevantes pra entender o que cada um acertou e errou. Salvei a pesquisa no <a href="https://github.com/akitaonrails/ai-memory/tree/main/docs"target="_blank" rel="noopener">próprio repo</a>:</p>
<ul>
<li><strong><a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/research-karpathy-llm-wiki.md"target="_blank" rel="noopener">research-karpathy-llm-wiki.md</a></strong> — o que Karpathy escreveu literal, separado do que a comunidade atribui (parafraseando) a ele. Três princípios fundamentais: compilação em vez de retrieval, três camadas (raw / wiki / schema), três operações (ingest / query / lint). Foi o blueprint conceitual.</li>
<li><strong><a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/research-agentmemory.md"target="_blank" rel="noopener">research-agentmemory.md</a></strong> — análise técnica do agentmemory: quatro níveis de consolidação (working → episodic → semantic → procedural), supersessão com <code>is_latest=false</code> em vez de delete, decaimento exponencial, reflexão por cluster. Mantive as ideias do agentmemory, mudei a implementação.</li>
<li><strong><a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/research-basic-memory.md"target="_blank" rel="noopener">research-basic-memory.md</a></strong> — Python, MCP-native, markdown puro no disco. O modelo é explicitamente manual: o agente chama <code>write_note</code> quando ele decide salvar. Bom modelo de storage, péssimo modelo de captura (depende do agente lembrar de chamar).</li>
<li><strong><a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/research-cognee.md"target="_blank" rel="noopener">research-cognee.md</a></strong> — pipeline de cognify (extract → chunk → graph + summarize → embed → persist) com knowledge graph + vector + relational triplo. Excelente pra agente de empresa rica que quer relação semântica entre entidades. Pesado demais pra &ldquo;memória de sessão de coding&rdquo;.</li>
</ul>
<p>A síntese que tirei: <strong>agentmemory tem as ideias certas. O resto é execução.</strong></p>
<p>A ideia que eu mais gosto do agentmemory, e que ele herdou parcialmente do LLM Wiki v2, é a <strong>consolidação tipo sono</strong>: memória curta retém detalhes brutos, memória longa vai consolidando e resumindo, e memória de longo prazo não acessada vai sendo esquecida. Não acumula lixo. Tem <code>accessCount</code>, decaimento exponencial geométrico, reforço por repetição espaçada. É similar e inspirado no que um humano faz dormindo.</p>
<p>No ai-memory eu mantive essa estrutura mas troquei a engenharia por baixo:</p>
<ul>
<li><strong>TypeScript → Rust em binário único.</strong> Sem KV remoto separado. Sem 3 processos. Um binário escrito em Rust, com um diretório de dados e um servidor Axum.</li>
<li><strong>Índice persistente em SQLite + FTS5.</strong> Nativo. Sem reindex a cada restart. WAL mode, busy_timeout 5s.</li>
<li><strong>Writer único em thread dedicada via mpsc.</strong> Sem janela de perda de 5s, sem timeout de 30s estourando.</li>
<li><strong>Markdown puro no disco como source of truth.</strong> SQLite é só índice derivado. Você pode abrir o wiki no Obsidian. Pode dar <code>grep</code>. Pode versionar com git (e ele versiona automaticamente).</li>
<li><strong>Isolamento por projeto na estrutura física do disco.</strong> <code>&lt;wiki_root&gt;/&lt;workspace_id&gt;/&lt;project_id&gt;/...</code> chaveado por UUIDs estáveis. Renomear projeto é update de uma coluna. Apagar projeto é <code>rm -rf</code> de um subdir. Mesmo nome de página em dois projetos não colide.</li>
<li><strong>Embedding e LLM são opcionais.</strong> Sem chave de provedor, o sistema ainda funciona com FTS5, expansão por links e fallback nas observações brutas. Adicione embeddings quando quiser re-ranking vetorial, e Claude Haiku ou GPT-5.4-mini quando quiser páginas consolidadas com qualidade.</li>
</ul>
<h2>Como funciona no dia a dia<span class="hx:absolute hx:-mt-20" id="como-funciona-no-dia-a-dia"></span>
    <a href="#como-funciona-no-dia-a-dia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A interação com os agentes é por dois mecanismos: <strong>hooks/extensões</strong> (captura automática) e <strong>MCP</strong> (consulta sob demanda).</p>
<p>Claude Code e Codex usam shell hooks. OpenCode usa plugin TypeScript. OMP/Pi usa extensão TypeScript. Cursor e Gemini CLI usam configurações JSON de lifecycle. Todos mandam eventos equivalentes (<code>SessionStart</code>, <code>UserPromptSubmit</code>, <code>PreToolUse</code>, <code>PostToolUse</code>, <code>PreCompact</code>, <code>Stop</code>, <code>SessionEnd</code>) como POST fire-and-forget pro servidor HTTP do ai-memory (rodando em <code>127.0.0.1:49374</code> por padrão). O agente nunca espera. Se o ai-memory tiver fora do ar, a sessão continua normal, só sem captura. Claude Desktop e OpenClaw entram como MCP-only: consultam memória, mas não capturam ciclo de vida automaticamente.</p>
<p>Do lado do agente, o MCP server expõe ferramentas que o LLM pode chamar quando achar útil:</p>
<table>
  <thead>
      <tr>
          <th>Você diz&hellip;</th>
          <th>Agente chama&hellip;</th>
          <th>O que acontece</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>&ldquo;Já discutimos X antes?&rdquo;</td>
          <td><code>memory_query</code></td>
          <td>FTS5 + vizinhos por link/graph RRF sobre páginas compiladas, re-ranking vetorial opcional e fallback em observações brutas quando o wiki não acha nada</td>
      </tr>
      <tr>
          <td>(antes de propor arquitetura) implícito</td>
          <td><code>memory_query</code></td>
          <td>Snippet de routing instrui o agente a checar decisões anteriores antes de propor algo novo</td>
      </tr>
      <tr>
          <td>&ldquo;Me coloca a par&rdquo; / &ldquo;fiquei fora um tempo&rdquo;</td>
          <td><code>memory_explore</code></td>
          <td>Digest em prosa. Verbosidade escala com tempo desde última atividade</td>
      </tr>
      <tr>
          <td>&ldquo;Onde paramos?&rdquo;</td>
          <td>(handoff já no contexto)</td>
          <td>Hook <code>SessionStart</code> já adicionou o handoff antes do primeiro prompt</td>
      </tr>
      <tr>
          <td>&ldquo;Salva contexto pra próxima sessão&rdquo;</td>
          <td><code>memory_handoff_begin</code></td>
          <td>Escreve um handoff terso com <code>open_questions</code> + <code>next_steps</code></td>
      </tr>
      <tr>
          <td>&ldquo;Consolida essa sessão&rdquo;</td>
          <td><code>memory_consolidate</code></td>
          <td>Trigger manual do que o SessionEnd faz automaticamente</td>
      </tr>
      <tr>
          <td>&ldquo;Audita o wiki&rdquo;</td>
          <td><code>memory_lint</code></td>
          <td>Detecta páginas obsoletas, contradições, sugere regras</td>
      </tr>
      <tr>
          <td>&ldquo;Quais são os stats?&rdquo;</td>
          <td><code>memory_status</code> / <code>memory_briefing</code></td>
          <td>Conta páginas, sessões, observações e atividade recente</td>
      </tr>
  </tbody>
</table>
<p>O snippet de routing que vai no <code>CLAUDE.md</code> (ou <code>AGENTS.md</code> pra Codex/OpenCode/Cursor/Gemini/OMP) é o que faz o agente saber quando chamar qual ferramenta. Ele se instala com um comando: peça pro agente &ldquo;instala o routing do ai-memory neste projeto&rdquo; e ele invoca a tool MCP <code>memory_install_self_routing</code>, recebe o snippet canônico, e escreve no arquivo certo. Idempotente.</p>
<h2>Exemplo de handoff entre agentes<span class="hx:absolute hx:-mt-20" id="exemplo-de-handoff-entre-agentes"></span>
    <a href="#exemplo-de-handoff-entre-agentes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eis o cenário concreto que motivou o projeto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ claude
&gt; &#34;Tô refatorando autenticação. A rotação de JWT tá quebrada, vou
   testar session cookies como alternativa.&#34;
[trabalha por uma hora]
&gt; /exit

$ codex   # mesmo diretório, horas ou dias depois
[SessionStart hook do ai-memory busca o handoff e prepende ao contexto]
&gt; &#34;Continuando: você estava investigando session cookies como
   alternativa à rotação de JWT quebrada. Posso seguir?&#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>Você não fez nada especial. No <code>SessionEnd</code> do Claude Code, o ai-memory consolidou as observações da sessão numa página <code>sessions/&lt;uuid&gt;.md</code>, criou um handoff tipado com <code>open_questions</code> + <code>next_steps</code>, e marcou como &ldquo;pending&rdquo;. No <code>SessionStart</code> do Codex, o servidor viu o handoff pending, recuperou e injetou no contexto inicial. O agente lê e continua.</p>
<p>Mesmo cenário se você abandona o projeto por dois meses e volta. O <code>memory_explore</code> calibra a verbosidade automaticamente: faz menos de uma hora desde a última sessão, retorna uma linha. Faz mais de 30 dias, retorna um resumo completo do que rolou.</p>
<h2>Adotando ai-memory num projeto que já existe<span class="hx:absolute hx:-mt-20" id="adotando-ai-memory-num-projeto-que-já-existe"></span>
    <a href="#adotando-ai-memory-num-projeto-que-j%c3%a1-existe" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se você instalar o ai-memory num projeto que já tem meses de história, o wiki começa vazio e as primeiras sessões são saldo zero. Você tá populando, não recuperando. O comando <code>ai-memory bootstrap</code> resolve isso. Ele coleta seu <code>git log</code>, README, <code>docs/</code>, cabeçalhos de módulo e arquivos de regras do projeto (<code>CLAUDE.md</code> / <code>AGENTS.md</code>), e manda pro servidor que usa o LLM pra gerar páginas-semente do wiki:</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="nb">cd</span> ~/Projects/meu-projeto
</span></span><span class="line"><span class="cl">ai-memory bootstrap</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>Custa cerca de $0.05 com Claude Haiku 4.5 (orçamento capado em 50K input tokens). Rode com <code>--dry-run</code> primeiro pra ver o que seria mandado sem pagar. A partir daí o wiki começa com contexto real, e cada sessão adiciona em cima.</p>
<h2>Instalação: três comandos<span class="hx:absolute hx:-mt-20" id="instalação-três-comandos"></span>
    <a href="#instala%c3%a7%c3%a3o-tr%c3%aas-comandos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu reduzi a instalação ao essencial. O caminho recomendado é Docker, porque o servidor fica isolado e a atualização é uma linha. Não precisa de mais nada:</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"># 1. Wrapper script (~3KB) que chama o binário dentro do container</span>
</span></span><span class="line"><span class="cl">mkdir -p ~/.local/bin
</span></span><span class="line"><span class="cl">curl -fsSL https://raw.githubusercontent.com/akitaonrails/ai-memory/main/bin/ai-memory <span class="se">\
</span></span></span><span class="line"><span class="cl">    -o ~/.local/bin/ai-memory
</span></span><span class="line"><span class="cl">chmod +x ~/.local/bin/ai-memory
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. Sobe o servidor (loopback-only, sem auth — single-user laptop)</span>
</span></span><span class="line"><span class="cl">docker run -d --name ai-memory <span class="se">\
</span></span></span><span class="line"><span class="cl">    --restart unless-stopped <span class="se">\
</span></span></span><span class="line"><span class="cl">    -p 127.0.0.1:49374:49374 <span class="se">\
</span></span></span><span class="line"><span class="cl">    -v ai-memory-data:/data <span class="se">\
</span></span></span><span class="line"><span class="cl">    -e <span class="nv">AI_MEMORY_LLM_PROVIDER</span><span class="o">=</span>anthropic <span class="se">\
</span></span></span><span class="line"><span class="cl">    -e <span class="nv">ANTHROPIC_API_KEY</span><span class="o">=</span>sk-ant-... <span class="se">\
</span></span></span><span class="line"><span class="cl">    akitaonrails/ai-memory:latest
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 3. Configura seu agente</span>
</span></span><span class="line"><span class="cl">ai-memory install-mcp   --client claude-code --apply
</span></span><span class="line"><span class="cl">ai-memory install-hooks --agent  claude-code --apply</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>Pronto. Abra o Claude Code como sempre. Cada prompt e tool call agora aterrissam no ai-memory, e a próxima sessão que você abrir no projeto vai ver um handoff com onde você parou.</p>
<p>Repita o passo 3 com <code>--agent codex</code>, <code>--agent opencode</code>, <code>--agent omp</code>/<code>pi</code>, <code>--client cursor</code>, <code>--client gemini-cli</code>, <code>--client pi</code>/<code>omp</code>, etc. pra outros agentes. Claude Desktop e OpenClaw são MCP-only. Idempotente: re-rodar substitui só a entrada do ai-memory, preserva qualquer outro hook/MCP que você tenha. Faz backup <code>.bak-&lt;timestamp&gt;</code> antes de cada escrita.</p>
<p>O README do projeto cobre as variações (<a href="https://github.com/akitaonrails/ai-memory#quick-start"target="_blank" rel="noopener">Docker compose</a>, homelab no LAN com bearer token, <a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/install.md"target="_blank" rel="noopener">instalação sem Docker</a>, build do fonte). Recomendo seguir o <a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/install.md"target="_blank" rel="noopener">docs/install.md</a> se seu setup foge do caminho padrão.</p>
<h2>Comandos úteis que vale conhecer<span class="hx:absolute hx:-mt-20" id="comandos-úteis-que-vale-conhecer"></span>
    <a href="#comandos-%c3%bateis-que-vale-conhecer" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de instalado, o dia a dia normal é só usar os agentes. Mas vale conhecer alguns comandos pra quando precisar:</p>
<ul>
<li><strong><code>ai-memory upgrade</code></strong> — auto-atualização do wrapper + <code>docker pull</code> da imagem nova + re-instalação dos hook scripts/plugins/extensões. Idempotente. Se você tem servidor remoto separado, refresca só o cliente local; o servidor você atualiza separadamente.</li>
<li><strong><code>ai-memory bootstrap</code></strong> — semeia o wiki a partir de git log + README + docs/ + regras do projeto. Pra projetos que já existem antes do ai-memory entrar.</li>
<li><strong><code>ai-memory purge-project --project &lt;nome&gt; --confirm</code></strong> — apaga um projeto inteiro. Atômico: linhas do SQLite, subdir do wiki, sem tocar nos vizinhos.</li>
<li><strong><code>ai-memory rename-project --from &lt;nome&gt; --to &lt;novo&gt;</code></strong> — renomeia. Uma coluna no DB, sem mover arquivo no disco. Sem efeito colateral.</li>
<li><strong><code>ai-memory backup</code></strong> — snapshot do diretório de dados (wiki + SQLite). <code>ai-memory restore</code> desfaz.</li>
<li><strong><code>ai-memory lint</code> / <code>ai-memory forget-sweep</code> / <code>ai-memory embed</code></strong> — manutenção manual. Lint e forget-sweep também rodam no agendamento do servidor por padrão; embedding backfill existe mas fica desligado por default porque pode chamar provedor pago.</li>
<li><strong><code>ai-memory generate-auth-token</code></strong> — quando você for expor o servidor além do loopback (ver <code>docs/deploy.md</code>).</li>
</ul>
<h2>Explorando o wiki no navegador<span class="hx:absolute hx:-mt-20" id="explorando-o-wiki-no-navegador"></span>
    <a href="#explorando-o-wiki-no-navegador" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O servidor também pode montar uma UI web read-only em <code>http://127.0.0.1:49374/web</code> quando sobe com <code>--enable-web</code>. Mesmo binário, mesma porta, sem processo extra. Serve pra três coisas práticas: auditar o que o agente vem salvando, conferir se as páginas consolidadas tão coerentes, e compartilhar uma página avulsa com alguém do time sem precisar dar acesso ao agente.</p>
<p>A homepage lista os projetos detectados, cada um com contagem de páginas e tempo desde a última atividade:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/23/ai-memory/ai-memory-web-projects.png" alt="Homepage do /web do ai-memory: cards de projeto (llm-coding-benchmark, nes-to-sms, ai-jail, .config, ai-memory, distrobox-gaming, scratch) cada um com contagem de páginas e “just now”, banner laranja avisando que é memória otimizada pra LLM, não documentação curada."  loading="lazy" /></p>
<p>Repare no aviso laranja no topo: o wiki é otimizado pra consumo por LLM, com páginas compiladas a partir de observações de sessão pelo LLM consolidador. Pra documentação canônica curada por humano, use o <code>docs/</code> próprio do projeto. O <code>/web</code> é pra &ldquo;spelunking&rdquo; (vasculhar o histórico).</p>
<p>Cada projeto é isolado por UUID na estrutura física do disco. Duas páginas com o mesmo path em projetos diferentes não colidem. Ao clicar num card você cai na árvore de páginas dele:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/23/ai-memory/ai-memory-web-project-view.png" alt="Página de projeto ai-memory: sidebar à esquerda com árvore agrupada por tipo (concepts/, decisions/, gotchas/), coluna direita lista atividade recente com badges de kind (fact, decision) e timestamps relativos."  loading="lazy" /></p>
<p>A sidebar mostra a árvore agrupada por tipo: <code>concepts/</code> (conhecimento durável), <code>decisions/</code> (decisões arquiteturais), <code>gotchas/</code> (pegadinhas), <code>_rules/</code> (regras de projeto sugeridas pro <code>CLAUDE.md</code>), <code>sessions/</code> (logs por sessão), e <code>bootstrap.md</code> (manifesto gerado pelo <code>ai-memory bootstrap</code>). A coluna direita mostra atividade recente.</p>
<p>Clicando em qualquer página, você cai no markdown renderizado:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/23/ai-memory/ai-memory-web-page.png" alt="Página individual “Per-project UUID-namespaced disk layout” renderizada: badges “semantic” e “fact” no topo, timestamps “updated just now” e “created 1 hour ago”, corpo em markdown com bloco de código ASCII mostrando a árvore de diretório."  loading="lazy" /></p>
<p>O rendering tem syntax highlighting, badges de tier (<code>working</code>/<code>episodic</code>/<code>semantic</code>/<code>procedural</code>) e kind (<code>decision</code>/<code>gotcha</code>/<code>rule</code>/<code>fact</code>), breadcrumbs, supersession chain visível. A busca combina FTS5, expansão por links e, se configurado, embeddings; modo claro/escuro segue a preferência do sistema via <code>prefers-color-scheme</code>.</p>
<p>Pra editar o wiki você abre os arquivos direto em qualquer editor de markdown (Obsidian funciona bem). A interface web é read-only por design: o LLM consolidador é quem escreve, humanos só leem.</p>
<h2>Por que Docker<span class="hx:absolute hx:-mt-20" id="por-que-docker"></span>
    <a href="#por-que-docker" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A escolha de Docker como caminho recomendado foi deliberada. Algumas vantagens práticas:</p>
<ul>
<li><strong>Atualização é uma linha.</strong> <code>ai-memory upgrade</code> puxa a imagem nova, refresca o wrapper, re-instala hooks/plugins/extensões. Você não compila Rust, não cuida de versão de libsqlite, não briga com mise/Node version churn como acontece com soluções baseadas em Node.</li>
<li><strong>Isolamento real.</strong> O servidor roda no container, lê só <code>/data</code> (volume nomeado), não polui seu sistema. Se quiser limpar, é <code>docker volume rm</code>.</li>
<li><strong>Mesma imagem em vários setups.</strong> Laptop, desktop, homelab. Mesma imagem amd64 ou arm64.</li>
<li><strong>Restart automático.</strong> <code>--restart unless-stopped</code> faz o servidor voltar quando você reinicia o Docker, ou quando liga a máquina (assumindo <code>systemctl enable docker</code>).</li>
<li><strong>Build de source não é necessário.</strong> Quem compila Rust toolchain 1.95+ leva uns 3 minutos em release. Pro usuário final, isso é cerimônia desnecessária. O Docker abstrai tudo.</li>
</ul>
<p>Tem caminho sem Docker no docs/install.md pra quem prefere. Mas Docker é onde a UX é melhor.</p>
<h2>Qual LLM usar pra consolidação<span class="hx:absolute hx:-mt-20" id="qual-llm-usar-pra-consolidação"></span>
    <a href="#qual-llm-usar-pra-consolida%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esse é um caso onde <strong>não precisa de modelo pesado</strong>. A consolidação é texto entra, texto sai: o agente registra observações brutas durante a sessão, no <code>SessionEnd</code> o LLM compila 1-5 páginas de wiki classificadas como <code>concept</code> / <code>decision</code> / <code>gotcha</code> / <code>rule</code>. Não tem raciocínio complexo, não tem código. Opus 4.7 ou GPT-5.5 thinking são overkill, custam caro e demoram muito pra uma tarefa que aceita modelo leve.</p>
<p>Pra escolher o default rodamos A/B testing sério em 6 provedores diferentes contra 5 fixtures sintéticas, cada uma desenhada pra estressar um modo de falha específico: extração multi-página, restrição (resistir a fabricar página quando a sessão é trivial), classificação correta de <code>rule</code> vs <code>gotcha</code> vs <code>decision</code>, e separação de tópicos não relacionados. A doc completa, com tabelas de parse rate, latência e qualidade por fixture, tá em <a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/llm-provider-comparison.md"target="_blank" rel="noopener"><code>docs/llm-provider-comparison.md</code></a>. Resumindo a tabela final:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Custo</th>
          <th>Latência</th>
          <th>Veredicto</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Claude Haiku 4.5</strong></td>
          <td>~$0.02/run</td>
          <td>7s</td>
          <td><strong>Default escolhido.</strong> Melhor classificação e restrição, sem alucinação.</td>
      </tr>
      <tr>
          <td>GPT-5.4-mini (OpenAI)</td>
          <td>~$0.005/run</td>
          <td>4s</td>
          <td>Alternativa mais barata e rápida. Leve over-classification em sessões triviais.</td>
      </tr>
      <tr>
          <td>qwen3:32b (Ollama local)</td>
          <td>$0</td>
          <td>92s</td>
          <td>Gratuito se você tem servidor local. Latência invisível em background.</td>
      </tr>
      <tr>
          <td>DeepSeek V4 Flash</td>
          <td>~$0.005/run</td>
          <td>22s</td>
          <td>Funciona, mas sem vantagem sobre GPT-mini ou Haiku.</td>
      </tr>
      <tr>
          <td>Sonnet 4.5</td>
          <td>~$0.06/run</td>
          <td>11s</td>
          <td>Substituído pelo Haiku. 3× o custo, mais alucinação.</td>
      </tr>
      <tr>
          <td>Kimi-K2.6</td>
          <td>n/a</td>
          <td>trava</td>
          <td>INELIGÍVEL. Modelo de raciocínio consome o budget de tokens internamente antes de emitir conteúdo.</td>
      </tr>
  </tbody>
</table>
<p>Por que Haiku ficou como default: hospedado (sempre disponível), o melhor em <strong>restrição</strong> (resiste a fabricar páginas quando a sessão é trivial, crucial pra wiki não virar coleção de páginas vazias) e o melhor em <strong>classificação</strong> (acerta <code>kind: rule</code> consistentemente, que é o que faz o roteamento automático pro <code>_rules/&lt;slug&gt;.md</code> funcionar). Custo de $0.02 por consolidação é desprezível pra uso pessoal.</p>
<p>Mas o usuário tem alternativas legítimas:</p>
<ul>
<li><strong>GPT-5.4-mini</strong> se o orçamento aperta (5× mais barato que Haiku, 2× mais rápido). O único defeito mensurável foi fabricar uma página <code>decisions/docs-spelling.md</code> numa sessão de correção de typo. Over-classification, mas leve.</li>
<li><strong>qwen3:32b no Ollama</strong> se você tem servidor local com ~20GB de RAM/VRAM disponível. Zero custo por consolidação, fidelidade compatível com os hospedados. A latência alta (~92s) não importa porque consolidação roda em background.</li>
<li><strong>DeepSeek V4 Flash</strong> funciona, mas o GPT-mini iguala em qualidade e bate em velocidade.</li>
</ul>
<p>Quem quiser testar outros modelos, o harness de avaliação tá em <a href="https://github.com/akitaonrails/ai-memory/tree/main/evals"target="_blank" rel="noopener"><code>evals/</code></a> como crate Rust separada. Adiciona um JSON de fixture, roda <code>cargo run -p ai-memory-eval --release</code>, compara dois provedores lado a lado. Os outputs ficam em <code>evals/runs/&lt;timestamp&gt;/{baseline,candidate}/</code> com o <code>.raw.txt</code> exato que o modelo respondeu mais o <code>.meta.json</code> com latência e parse rate. PRs com novos modelos testados são muito bem-vindos.</p>
<h2>Status: beta, falta usuário<span class="hx:absolute hx:-mt-20" id="status-beta-falta-usuário"></span>
    <a href="#status-beta-falta-usu%c3%a1rio" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O ai-memory tá nas tags <code>v0.2-complete</code> no GitHub. As funcionalidades principais funcionam: captura automática, MCP, web UI opcional, bootstrap, purge/rename/backup, bearer auth, browse, busca FTS5 + graph/link-neighbour RRF, embeddings opcionais, consolidação, decay, manutenção agendada e handoff entre Claude Code / Codex / OpenCode / Cursor / Gemini CLI / OMP-Pi. Eu rodo em produção pessoal há algumas semanas.</p>
<p>Mas é beta. Precisa de muito mais usuário batendo nele pra eu encontrar os bugs que ainda não aparecem na minha máquina. Claude Desktop e OpenClaw funcionam como MCP-only; alguns clientes MCP exóticos ainda precisam de teste real. A integração mais polida hoje é Claude Code, seguida de Codex, OpenCode e OMP/Pi.</p>
<p>Se você tá lendo até aqui, considera instalar e me mandar feedback. Issues no <a href="https://github.com/akitaonrails/ai-memory/issues"target="_blank" rel="noopener">github.com/akitaonrails/ai-memory/issues</a>. PRs são muito bem-vindos — o código é Rust, MIT, e a arquitetura tá documentada em <a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/ARCHITECTURE.md"target="_blank" rel="noopener"><code>docs/ARCHITECTURE.md</code></a> e <a href="https://github.com/akitaonrails/ai-memory/blob/main/docs/design-decisions.md"target="_blank" rel="noopener"><code>docs/design-decisions.md</code></a>. Mesmo bug report de &ldquo;instalei, não rodou no meu setup&rdquo; ajuda — significa que tem algo que assumi sobre ambientes que não bate com o seu.</p>
<p>A motivação central continua sendo a frase do Karpathy. Memória de agente é texto. Texto fica no disco. Indexa com SQLite. Compila com LLM quando vale o trabalho. Esquece quando ninguém olha mais. Sai do caminho. E quando você troca de agente no meio do projeto, o próximo já sabe onde você parou.</p>
<p>É isso que o ai-memory tenta entregar.</p>
]]></content:encoded><category>ai-memory</category><category>agentes-de-codigo</category><category>rust</category></item><item><title>Reescrevi o OpenClaw em Rust, funcionou? | FrankClaw</title><link>https://akitaonrails.github.io/2026/03/16/reescrevi-o-openclaw-em-rust-funcionou-frankclaw/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/03/16/reescrevi-o-openclaw-em-rust-funcionou-frankclaw/</guid><pubDate>Mon, 16 Mar 2026 08:00:00 -0300</pubDate><description>&lt;p&gt;Antes de tudo: o FrankClaw ainda está em alpha pesado. Funciona pra tarefas simples, mas não testei workflows complexos. Se você quer ajudar, abre Issues no &lt;a href="https://github.com/akitaonrails/frankclaw"target="_blank" rel="noopener"&gt;GitHub&lt;/a&gt; com o que encontrar. Tem muita coisa pra testar.&lt;/p&gt;
&lt;p&gt;Até &amp;ldquo;funciona&amp;rdquo;, mas esse projeto foi mais pelo exercício. Dito isso, deixa eu contar por que fiz isso.&lt;/p&gt;
&lt;h2&gt;O problema com o OpenClaw&lt;span class="hx:absolute hx:-mt-20" id="o-problema-com-o-openclaw"&gt;&lt;/span&gt;
&lt;a href="#o-problema-com-o-openclaw" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;O &lt;a href="https://github.com/openclaw/openclaw"target="_blank" rel="noopener"&gt;OpenClaw&lt;/a&gt; é um gateway que conecta canais de mensagem (Telegram, Discord, Slack, WhatsApp, etc.) a provedores de IA (OpenAI, Anthropic, Ollama). Você configura, sobe o servidor, e pode conversar com LLMs direto pelo Telegram ou qualquer outro canal. É um projeto popular e com bastante atividade.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Antes de tudo: o FrankClaw ainda está em alpha pesado. Funciona pra tarefas simples, mas não testei workflows complexos. Se você quer ajudar, abre Issues no <a href="https://github.com/akitaonrails/frankclaw"target="_blank" rel="noopener">GitHub</a> com o que encontrar. Tem muita coisa pra testar.</p>
<p>Até &ldquo;funciona&rdquo;, mas esse projeto foi mais pelo exercício. Dito isso, deixa eu contar por que fiz isso.</p>
<h2>O problema com o OpenClaw<span class="hx:absolute hx:-mt-20" id="o-problema-com-o-openclaw"></span>
    <a href="#o-problema-com-o-openclaw" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/openclaw/openclaw"target="_blank" rel="noopener">OpenClaw</a> é um gateway que conecta canais de mensagem (Telegram, Discord, Slack, WhatsApp, etc.) a provedores de IA (OpenAI, Anthropic, Ollama). Você configura, sobe o servidor, e pode conversar com LLMs direto pelo Telegram ou qualquer outro canal. É um projeto popular e com bastante atividade.</p>
<p>Atividade demais, na verdade.</p>
<p>Eu fiz um clone depth-1 do repositório e rodei um <code>tokei</code>:</p>
<table>
  <thead>
      <tr>
          <th>Métrica</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Arquivos TypeScript (sem testes)</td>
          <td>3.794</td>
      </tr>
      <tr>
          <td>Linhas de código TypeScript</td>
          <td>~1.247.000</td>
      </tr>
      <tr>
          <td>Arquivos de teste</td>
          <td>2.799</td>
      </tr>
      <tr>
          <td>Dependências (package.json raiz)</td>
          <td>73</td>
      </tr>
  </tbody>
</table>
<p>Mais de um milhão de linhas de TypeScript. Os 2.799 arquivos de teste parecem bastante em número absoluto, mas proporcionalmente ao tamanho do codebase, a cobertura é baixa. A maior parte do código está em 29 pacotes de um monorepo com 21 extensões.</p>
<p>Fui buscar mais commits pra entender o ritmo de desenvolvimento. Nos 100 commits que consegui puxar, todos caíram em apenas 2 dias (9 e 10 de março). ~50 commits por dia, de 42 contribuidores diferentes. Vibe coding ao extremo.</p>
<p>A conclusão é a que você imagina: volumes enormes de código gerado por IA sendo despejados num repositório a uma velocidade que impossibilita review humano sério. E isso me incomodou o suficiente pra ir investigar mais a fundo.</p>
<h2>A auditoria de segurança<span class="hx:absolute hx:-mt-20" id="a-auditoria-de-segurança"></span>
    <a href="#a-auditoria-de-seguran%c3%a7a" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pedi pro Claude fazer uma auditoria de segurança completa no código do OpenClaw. O <a href="https://github.com/akitaonrails/frankclaw/blob/master/docs/OPENCLAW_SECURITY_AUDIT.md"target="_blank" rel="noopener">relatório</a> encontrou:</p>
<table>
  <thead>
      <tr>
          <th>Severidade</th>
          <th>Quantidade</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CRITICAL</td>
          <td>7</td>
      </tr>
      <tr>
          <td>HIGH</td>
          <td>9</td>
      </tr>
      <tr>
          <td>MEDIUM</td>
          <td>12</td>
      </tr>
      <tr>
          <td>LOW</td>
          <td>6</td>
      </tr>
      <tr>
          <td>INFO</td>
          <td>5</td>
      </tr>
  </tbody>
</table>
<p>Sete vulnerabilidades críticas. Deixa eu listar algumas:</p>
<ul>
<li>Timing side-channel na comparação de tokens &ndash; o <code>safeEqualSecret()</code> faz um <code>return</code> antecipado no type-check, permitindo que um atacante distinga tokens malformados de tokens errados medindo latência.</li>
<li><code>eval()</code> no browser tool &ndash; execução arbitrária de JavaScript sem sandbox.</li>
<li>Shell sem allowlist &ndash; qualquer tool pode executar qualquer comando no sistema.</li>
<li>Webhooks do Slack sem verificação de assinatura nenhuma.</li>
<li>Transcripts e config em texto puro no disco, sem criptografia.</li>
<li>Sem rate limiting efetivo &ndash; IPs podem ser spoofados se o operador configura trusted proxies de forma ampla.</li>
</ul>
<p>Essas são só as que o Claude encontrou numa varredura automatizada. Provavelmente tem mais.</p>
<p>Eu não vou rodar isso na minha máquina. Nem dentro de um container Docker. Um gateway que recebe webhooks da internet, executa comandos shell, se conecta a APIs de IA com suas chaves, e guarda histórico de conversas, tudo isso com 7 vulnerabilidades críticas conhecidas? Não, obrigado.</p>
<p>Então eu fiz o que qualquer desenvolvedor racional faria: decidi construir o meu.</p>
<h2>A primeira tentativa: Claude Code<span class="hx:absolute hx:-mt-20" id="a-primeira-tentativa-claude-code"></span>
    <a href="#a-primeira-tentativa-claude-code" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Comecei do jeito mais direto. Clonei o OpenClaw, apontei o Claude Code pro código, e pedi: &ldquo;reescreve isso em Rust.&rdquo;</p>
<p>Não funcionou. O codebase é grande demais. Mais de um milhão de linhas de TypeScript espalhadas em 29 pacotes. O Claude não consegue manter tudo em contexto. O resultado inicial foi muito incompleto: muitos tipos criados mas sem implementação, <code>todo!()</code> por toda parte, boilerplate demais e funcionalidade de menos.</p>
<p>Troquei pro Codex 5.4 pra testar. Mesma coisa: pedi pra analisar e reescrever. Melhorou um pouco em certos aspectos, mas o problema fundamental é o mesmo. Nenhuma IA hoje pega um projeto desse tamanho e reescreve inteiro de uma vez. O contexto não cabe.</p>
<h2>A técnica que funciona<span class="hx:absolute hx:-mt-20" id="a-técnica-que-funciona"></span>
    <a href="#a-t%c3%a9cnica-que-funciona" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O que funciona é ir devagar. Um passo de cada vez.</p>
<p>Pede pro Claude (ou Codex) analisar o código original em etapas. Faz um plano longo detalhando cada feature. Depois implementa uma feature por vez em Rust, com testes, faz commit, e repete. É tedioso, mas é o que produz código que compila e funciona de verdade.</p>
<p>O motivo é simples: o código original é tão massivo que nenhum agente de IA (nem vários em paralelo) consegue manter tudo em contexto ao mesmo tempo. Você precisa decidir o que importa, implementar aquilo, validar, e seguir pro próximo.</p>
<p>E precisa decidir o que cortar. O OpenClaw tem 21 extensões de canal: Google Chat, iMessage, IRC, Teams, Matrix, Mattermost, Nostr, Twitch&hellip; Eu não preciso de nenhuma dessas. Mantive os canais mainstream: Web, Telegram, Discord, Slack, Signal, WhatsApp e Email. TTS? Fora. Polls? Fora. WhatsApp Web via Baileys? Fora, uso a Cloud API oficial. São features que adicionam complexidade sem valor proporcional.</p>
<h2>Descobrindo o IronClaw<span class="hx:absolute hx:-mt-20" id="descobrindo-o-ironclaw"></span>
    <a href="#descobrindo-o-ironclaw" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No meio do desenvolvimento, descobri o <a href="https://github.com/nichochar/iron-claw"target="_blank" rel="noopener">IronClaw</a>, que se apresenta como &ldquo;OpenClaw em Rust&rdquo;. Ótimo, pensei. Vou ver o que eles fizeram.</p>
<p>Clonei o repositório e pedi pro Claude escrever um <a href="https://github.com/akitaonrails/frankclaw/blob/master/docs/IRONCLAW_COMPARISON.md"target="_blank" rel="noopener">relatório de comparação</a>. O IronClaw tem coisas boas. Adotei 12 features:</p>
<p>Circuit breaker com retry e backoff exponencial pra resiliência de providers LLM. Detecção de leak de credenciais no output. Cache de respostas LLM com SHA-256 da prompt. Cost tracking com budget guards (aviso em 80%, bloqueio em 100%). Extended thinking pra Claude 3.7+ e o1. MCP client pra tool servers externos. Lifecycle hooks em inbound, tool calls e outbound. Smart model routing que joga queries simples pra modelos mais baratos. Suporte a túneis (cloudflared, ngrok, tailscale). REPL interativo (<code>frankclaw chat</code>). Routines com event triggers além de cron. Job state machine com auto-repair.</p>
<p>Mas o IronClaw depende de PostgreSQL + pgvector, tem sandbox WASM (wasmtime adiciona ~10MB), e é parte do ecossistema NEAR AI. Eu quero um binário único com SQLite embarcado e zero dependências externas.</p>
<h2>O que o FrankClaw traz do OpenClaw<span class="hx:absolute hx:-mt-20" id="o-que-o-frankclaw-traz-do-openclaw"></span>
    <a href="#o-que-o-frankclaw-traz-do-openclaw" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O core do OpenClaw está lá: 7 canais de mensagem, multi-provider com failover, agent runtime, sistema de comandos, skills, subagents, browser automation via CDP, bash tool com allowlist, cron jobs, REPL interativo. Mais as 12 features do IronClaw que listei acima.</p>
<p>Mas vários pedaços precisaram ser reescritos de um jeito que o original não fez. A compactação de contexto, por exemplo, usa sliding window com estimativa de tokens, pruning de mensagens e repair automático de tool pairs que ficam órfãos quando o contexto é cortado. O failover entre providers agora é model-aware: se você pede um modelo Claude e o provider da vez é OpenAI, ele pula automaticamente em vez de dar erro. O canvas renderiza SVG, HTML e Markdown com detecção de conflitos de revisão. São coisas que no OpenClaw ou não existiam ou estavam pela metade.</p>
<p>Depois veio a fase de adicionar o que faltava pra paridade. O FrankClaw hoje tem 30+ LLM tools: <code>web_fetch</code> (SSRF-safe com HTML-to-text), <code>web_search</code> (Brave API), <code>file_read</code>/<code>file_write</code>/<code>file_edit</code> (sandboxed no workspace com proteção contra path traversal), <code>pdf_read</code>, <code>image_describe</code> (via modelos de vision), <code>audio_transcribe</code>, <code>sessions_list</code>/<code>sessions_history</code>/<code>sessions_delete</code>, <code>message_send</code>/<code>message_react</code>, <code>cron_list</code>/<code>cron_add</code>/<code>cron_remove</code>, <code>config_get</code> (auto-redação de secrets), <code>agents_list</code>, <code>memory_get</code>/<code>memory_search</code>, mais os browser tools (<code>browser_open</code>, <code>browser_extract</code>, <code>browser_snapshot</code>, <code>browser_click</code>, <code>browser_type</code>, <code>browser_wait</code>, <code>browser_press</code>, <code>browser_sessions</code>, <code>browser_close</code>, <code>browser_aria</code>).</p>
<p>Algumas adições que não existem no OpenClaw original: um sistema de memória/RAG com SQLite FTS5 e embeddings (OpenAI, Gemini, Voyage) que sincroniza arquivos do workspace automaticamente. Uma API compatível com OpenAI (<code>/v1/chat/completions</code> e <code>/v1/models</code>), então qualquer cliente que fala esse protocolo (Cursor, Continue, Open WebUI) pode usar o FrankClaw como backend sem adaptação. Uma TUI em <code>ratatui</code> pra quem prefere terminal. Aprovação interativa de tools destrutivos antes de executar.</p>
<p>Tem coisas menores que fazem diferença na prática. Você pode configurar múltiplas API keys por provider com round-robin e backoff automático, então se uma key estourar o rate limit, a próxima assume. O model catalog já sabe context windows e custos dos modelos OpenAI e Anthropic sem você ter que configurar. Extração de URLs de mensagens tem blocklist de IPs privados contra SSRF. O sistema de comandos aceita directives inline (<code>/think</code>, <code>/model</code>) além de aliases.</p>
<p>Do lado de operação: ACP (Agent Client Protocol) via JSON-RPC 2.0 sobre NDJSON pra quem quer integrar programaticamente. Sistema de plugins com manifests e lifecycle de enable/disable. i18n com 9 locales via <code>FRANKCLAW_LANG</code>. Workspace identity files (<code>SOUL.md</code>, <code>IDENTITY.md</code>) pra definir a personalidade do bot por projeto. Health monitor nos canais com auto-restart. WebSocket com ping keepalive que sobrevive timeouts de proxy e túnel. <code>frankclaw start/stop/status</code> pra quem quer rodar como daemon com PID tracking. E toda a configuração migrou de JSON pra TOML.</p>
<h2>Hardening: onde está o diferencial<span class="hx:absolute hx:-mt-20" id="hardening-onde-está-o-diferencial"></span>
    <a href="#hardening-onde-est%c3%a1-o-diferencial" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/akitaonrails/frankclaw/blob/master/docs/OPENCLAW_SECURITY_AUDIT.md"target="_blank" rel="noopener">relatório de auditoria</a> que rodamos no OpenClaw encontrou 7 vulnerabilidades críticas e 9 high. O FrankClaw resolve todas:</p>
<table>
  <thead>
      <tr>
          <th>Área</th>
          <th>OpenClaw</th>
          <th>FrankClaw</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Comparação de tokens</td>
          <td>SHA-256 + timingSafeEqual com early return que leaka timing</td>
          <td>Comparação constant-time byte a byte, sem early returns</td>
      </tr>
      <tr>
          <td>Shell execution</td>
          <td>Sem allowlist obrigatória</td>
          <td>Deny-all por padrão + binary allowlist + rejeição de metacaracteres + sandbox ai-jail opcional</td>
      </tr>
      <tr>
          <td>Browser tool</td>
          <td><code>eval()</code> sem sandbox</td>
          <td>CDP com timeout 15s, SSRF guard, crash recovery, ARIA inspection</td>
      </tr>
      <tr>
          <td>Webhook Slack</td>
          <td>Zero verificação de assinatura</td>
          <td>HMAC-SHA256 com proteção contra replay</td>
      </tr>
      <tr>
          <td>Webhook Discord</td>
          <td>Placeholder hardcoded</td>
          <td>Ed25519 com validação de timestamp</td>
      </tr>
      <tr>
          <td>Criptografia</td>
          <td>Plaintext no disco</td>
          <td>ChaCha20-Poly1305 em sessions e config</td>
      </tr>
      <tr>
          <td>Password hashing</td>
          <td>Sem autenticação por senha</td>
          <td>Argon2id (t=3, m=64MB, p=4)</td>
      </tr>
      <tr>
          <td>Permissões de arquivo</td>
          <td>0o644 (world-readable)</td>
          <td>0o600 (owner-only)</td>
      </tr>
      <tr>
          <td>Prompt injection</td>
          <td>Sanitização básica</td>
          <td>Unicode Cc/Cf stripping + boundary tags + limite de 2MB</td>
      </tr>
      <tr>
          <td>Malware scanning</td>
          <td>Nenhum</td>
          <td>VirusTotal opcional em uploads</td>
      </tr>
      <tr>
          <td>Validação de input</td>
          <td>Sem limites</td>
          <td>IDs de 255 bytes, session keys de 800 bytes, frames WS configuráveis</td>
      </tr>
      <tr>
          <td>SSRF</td>
          <td>Proteção parcial</td>
          <td>Blocklist completa (RFC 1918, loopback, CGNAT, link-local) + DNS rebinding defense</td>
      </tr>
      <tr>
          <td>Tool execution</td>
          <td>Sem confirmação do usuário</td>
          <td>Aprovação interativa pra tools mutating/destructive</td>
      </tr>
  </tbody>
</table>
<p>O FrankClaw compila com <code>#![forbid(unsafe_code)]</code> em todos os 13 crates. Zero blocos unsafe.</p>
<p>E a auditoria não parou no OpenClaw. Fizemos uma <a href="https://github.com/akitaonrails/frankclaw/blob/master/docs/AUDIT_PLAN.md"target="_blank" rel="noopener">auditoria por componente</a> em 14 fases comparando cada parte do FrankClaw contra o original: canais, providers, runtime, tools, sessions, crypto, cron, webhooks. Tudo documentado.</p>
<h2>Deploy: como instalar<span class="hx:absolute hx:-mt-20" id="deploy-como-instalar"></span>
    <a href="#deploy-como-instalar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O FrankClaw roda com Docker Compose. Três containers: gateway, Chromium headless (pra browser tools), e Cloudflare tunnel (pra receber webhooks).</p>
<h3>1. Clonar e configurar<span class="hx:absolute hx:-mt-20" id="1-clonar-e-configurar"></span>
    <a href="#1-clonar-e-configurar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><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">git clone https://github.com/akitaonrails/frankclaw.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> frankclaw
</span></span><span class="line"><span class="cl">cp .env.docker.example .env.docker</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>Edite o <code>.env.docker</code> com suas chaves:</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"># Providers de IA (preencha os que usar)</span>
</span></span><span class="line"><span class="cl"><span class="nv">OPENAI_API_KEY</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="nv">ANTHROPIC_API_KEY</span><span class="o">=</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Canais (preencha os que quiser usar)</span>
</span></span><span class="line"><span class="cl"><span class="nv">TELEGRAM_BOT_TOKEN</span><span class="o">=</span>         <span class="c1"># via @BotFather</span>
</span></span><span class="line"><span class="cl"><span class="nv">WHATSAPP_TOKEN</span><span class="o">=</span>             <span class="c1"># Meta Business Platform</span>
</span></span><span class="line"><span class="cl"><span class="nv">WHATSAPP_PHONE_ID</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="nv">WHATSAPP_VERIFY_TOKEN</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="nv">DISCORD_BOT_TOKEN</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="nv">SLACK_BOT_TOKEN</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="nv">SLACK_APP_TOKEN</span><span class="o">=</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Embedding providers (só se usar memória/RAG)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># GEMINI_API_KEY=</span>
</span></span><span class="line"><span class="cl"><span class="c1"># VOYAGE_API_KEY=</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Opcional: criptografia de sessions (recomendado)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Gere com: openssl rand -base64 32</span>
</span></span><span class="line"><span class="cl"><span class="nv">FRANKCLAW_MASTER_KEY</span><span class="o">=</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Opcional: scan de malware em uploads</span>
</span></span><span class="line"><span class="cl"><span class="nv">VIRUSTOTAL_API_KEY</span><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>
<h3>2. Configurar o gateway<span class="hx:absolute hx:-mt-20" id="2-configurar-o-gateway"></span>
    <a href="#2-configurar-o-gateway" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O arquivo <code>frankclaw.toml</code> define agentes, modelos e canais. Use o wizard ou os exemplos:</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"># Gerar config base com canal web</span>
</span></span><span class="line"><span class="cl">cargo run -- onboard --channel web
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Ou copiar dos exemplos</span>
</span></span><span class="line"><span class="cl">ls examples/channels/</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 cada canal, o CLI tem templates prontos:</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">cargo run -- config-example --channel telegram
</span></span><span class="line"><span class="cl">cargo run -- config-example --channel whatsapp</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>
<h3>3. Cloudflare Tunnel (pra receber webhooks)<span class="hx:absolute hx:-mt-20" id="3-cloudflare-tunnel-pra-receber-webhooks"></span>
    <a href="#3-cloudflare-tunnel-pra-receber-webhooks" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você vai usar canais que precisam de webhook (Telegram, Discord, Slack, WhatsApp), precisa de um túnel público. O Docker Compose já inclui o cloudflared:</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"># Copie suas credenciais do Cloudflare</span>
</span></span><span class="line"><span class="cl">cp docker/cloudflared/config.yml.example docker/cloudflared/config.yml
</span></span><span class="line"><span class="cl">cp ~/.cloudflared/&lt;tunnel-id&gt;.json docker/cloudflared/credentials.json
</span></span><span class="line"><span class="cl">cp ~/.cloudflared/cert.pem docker/cloudflared/cert.pem
</span></span><span class="line"><span class="cl"><span class="c1"># Edite config.yml com seu hostname</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>
<h3>4. Subir<span class="hx:absolute hx:-mt-20" id="4-subir"></span>
    <a href="#4-subir" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><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">docker compose up -d</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 gateway sobe na porta 18789 (interna ao Docker). O cloudflared roteia o tráfego externo. O Chromium fica na rede interna pra browser tools.</p>
<p>Pra testar local sem Docker:</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">cargo run -- chat</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>Abre o REPL interativo direto no terminal (agora também tem uma TUI em <code>ratatui</code> com dark mode e tabs). Sem gateway, sem webhook. Bom pra validar que o provider de IA está respondendo antes de configurar canais.</p>
<h3>Validação<span class="hx:absolute hx:-mt-20" id="validação"></span>
    <a href="#valida%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><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">cargo run -- check     <span class="c1"># valida config</span>
</span></span><span class="line"><span class="cl">cargo run -- doctor    <span class="c1"># diagnóstico completo</span>
</span></span><span class="line"><span class="cl">cargo run -- audit     <span class="c1"># auditoria de segurança com severity ratings</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 <code>audit</code> é o que eu mais gosto. Ele verifica se você tem criptografia habilitada, se as permissões de arquivo estão corretas, se os webhooks têm verificação de assinatura, se o bash tool está em deny-all. Sai com exit code non-zero se encontra problemas críticos, então dá pra colocar no CI.</p>
<h2>O processo de desenvolvimento<span class="hx:absolute hx:-mt-20" id="o-processo-de-desenvolvimento"></span>
    <a href="#o-processo-de-desenvolvimento" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O projeto tem 178 commits em ~5 dias de trabalho (10-16 de março). Quase 57 mil linhas de Rust em 120 arquivos, organizados em 13 crates.</p>
<p>Os commits contam a história. As primeiras dezenas foram scaffold: estrutura do workspace, tipos básicos, o gateway HTTP/WebSocket, primeira versão dos channel adapters. Código gerado em massa, muita coisa incompleta.</p>
<p>Depois começaram os adapters de canal, um por um:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>236aa1c - Minimal Discord channel adapter
fd67017 - Minimal Slack channel adapter
9f51373 - Minimal Signal channel adapter
1052e47 - WhatsApp channel webhook adapter
035f86e - Email channel adapter (IMAP inbound, SMTP outbound)</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>Quando os canais básicos estavam no lugar, veio a integração do IronClaw num commit grande:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>c87ab32 - IronClaw-derived features: circuit breaker, retry, leak detection, cache, cost tracking, extended thinking</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>E aí veio o hardening. Essa foi a fase que eu mais tive que intervir manualmente, porque o Claude gera código funcional mas não pensa em vetores de ataque sozinho:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>db34198 - Prompt injection sanitization, external content wrapping, prompt size limit
5719b34 - Optional VirusTotal malware scanning for file uploads
ccd2b2b - Harden input validation across all user-facing entry points
aa918ee - Optional ai-jail sandbox for bash tool
2d7b1df - Security audit command with severity-rated findings
d12cc97 - 3-tier ToolRiskLevel system replacing binary browser mutation flag
21e0c91 - Timing-safe token comparison in WhatsApp, crypto audit tests
e240c1b - Webhook replay prevention with timestamp verification
876a78c - Gateway &amp; media: SSRF redirect validation, filename hardening</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>22 commits de security hardening. Mais 10 de auditoria por componente. Cada achado virou um commit com fix.</p>
<p>Depois vieram as auditorias específicas por canal, cada uma descobrindo edge cases diferentes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>43b085f - Discord audit: HELLO timeout, fatal close codes, message chunking
12c7cff - Telegram audit: caption overflow, parse fallback, edit idempotency
f515062 - WhatsApp audit: message type filtering, send error classification
3c42aff - Slack audit: fatal auth errors, send error classification</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>Browser tools precisaram de atenção extra. Um headless Chrome que recebe URLs de um LLM é um vetor de ataque óbvio:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>3217a96 - Browser automation: CDP timeout, SSRF guard, session limits, crash recovery
d98a803 - Gate mutating browser tools behind operator approval
014f56e - Browser screenshot/ARIA tools for accessibility tree inspection</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>Nos commits mais recentes, o projeto começou a divergir do OpenClaw:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>5d73c4d - OpenAI-compatible HTTP API (/v1/chat/completions, /v1/models)
d832c36 - Memory/RAG system with SQLite FTS5, embeddings, and file sync
2b05f47 - Interactive tool approval for mutating/destructive tools
49034eb - Web console: dark mode, 8 tabs, focus mode, tool sidebar
9f51a18 - TUI, Gemini/Voyage embeddings, plugin management, ACP protocol
3c0703b - Config migration from JSON to TOML
eb130ad - Channel health monitor with auto-restart and rate limiting
2c3ea7e - Workspace bootstrap files (SOUL.md, IDENTITY.md) to system prompt
9df61bf - Model-aware failover routing, canvas SVG rendering
c7ba108 - WebSocket ping keepalive and auto-reconnect to web console</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 API compatível com OpenAI é a que eu mais uso no dia a dia. Cursor, Continue, Open WebUI, qualquer coisa que fala o protocolo OpenAI consegue usar o FrankClaw como backend sem mexer em nada do lado do cliente.</p>
<h2>Os números<span class="hx:absolute hx:-mt-20" id="os-números"></span>
    <a href="#os-n%c3%bameros" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><table>
  <thead>
      <tr>
          <th>Métrica</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Commits</td>
          <td>178</td>
      </tr>
      <tr>
          <td>Dias de trabalho</td>
          <td>~5</td>
      </tr>
      <tr>
          <td>Linhas de Rust</td>
          <td>56.586</td>
      </tr>
      <tr>
          <td>Arquivos Rust</td>
          <td>120</td>
      </tr>
      <tr>
          <td>Crates</td>
          <td>13</td>
      </tr>
      <tr>
          <td>LLM tools</td>
          <td>30+</td>
      </tr>
      <tr>
          <td>Commits de security hardening</td>
          <td>22</td>
      </tr>
      <tr>
          <td>Commits de auditoria</td>
          <td>10</td>
      </tr>
      <tr>
          <td>Canais suportados</td>
          <td>7</td>
      </tr>
      <tr>
          <td>Providers de IA</td>
          <td>9 (OpenAI, Anthropic, Ollama, Google, OpenRouter, Copilot, Groq, Together, DeepSeek)</td>
      </tr>
      <tr>
          <td>Vulnerabilidades críticas do OpenClaw resolvidas</td>
          <td>7/7</td>
      </tr>
      <tr>
          <td>Vulnerabilidades high do OpenClaw resolvidas</td>
          <td>9/9</td>
      </tr>
      <tr>
          <td>Blocos unsafe no código</td>
          <td>0</td>
      </tr>
  </tbody>
</table>
<p>Comparado ao OpenClaw:</p>
<table>
  <thead>
      <tr>
          <th>Métrica</th>
          <th>OpenClaw (TS)</th>
          <th>FrankClaw (Rust)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Linhas de código</td>
          <td>~1.247.000</td>
          <td>56.586</td>
      </tr>
      <tr>
          <td>Arquivos fonte</td>
          <td>3.794</td>
          <td>120</td>
      </tr>
      <tr>
          <td>Dependências runtime</td>
          <td>73</td>
          <td>~40 crates</td>
      </tr>
      <tr>
          <td>Canais</td>
          <td>28</td>
          <td>7</td>
      </tr>
      <tr>
          <td>Vulnerabilidades críticas</td>
          <td>7 conhecidas</td>
          <td>0</td>
      </tr>
  </tbody>
</table>
<p>Os números não são diretamente comparáveis. O OpenClaw tem 21 extensões de canal que eu cortei, uma UI web mais completa, e features de nicho que não portei. Mas o core (gateway, canais mainstream, providers, runtime, sessions, tools, memória) está lá com 22x menos código.</p>
<h2>Como ajudar (beta testing)<span class="hx:absolute hx:-mt-20" id="como-ajudar-beta-testing"></span>
    <a href="#como-ajudar-beta-testing" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O FrankClaw funciona pra conversa básica via Web e Telegram (testei ambos). WhatsApp funciona pra mensagens simples. Discord, Slack, Signal e Email estão implementados mas não tiveram teste extensivo. Não fizemos nenhum workflow complicado ainda.</p>
<p>Se você quer testar: clone o repositório, suba com Docker Compose, configure pelo menos um canal (Telegram é o mais fácil) e tente usar normalmente. Mande mensagens, teste tool calls, tente quebrar o sistema. Abra Issues no GitHub com o que encontrar.</p>
<p>O que eu sei que precisa de mais olhos: workflows com tools (browser, bash, MCP), subagent orchestration, failover entre providers, session persistence com criptografia, smart routing entre modelos, scheduled jobs, o sistema de memória/RAG, e a API OpenAI-compatible. Basicamente tudo que vai além de &ldquo;manda mensagem, recebe resposta&rdquo;.</p>
<p>Não precisa ser desenvolvedor Rust. O valor maior está em usar o sistema de formas que eu não pensei e encontrar os edge cases que só aparecem com uso real.</p>
<p>O FrankClaw não substitui o OpenClaw hoje. O OpenClaw tem mais canais, mais features, mais gente trabalhando nele. Mas carrega o peso de mais de um milhão de linhas de TypeScript geradas a 50 commits por dia por dezenas de contribuidores, com vulnerabilidades críticas documentadas. O FrankClaw é a alternativa pra quem olha pra isso e pensa &ldquo;eu não vou rodar esse código na minha máquina&rdquo;.</p>
<p>Mas vou ser honesto: apesar de ter sido divertido construir, eu pessoalmente não sei se preciso disso. O FrankClaw é um gateway genérico, feito pra ser flexível, conectar qualquer canal a qualquer provider, com runtime de agentes, tools, subagents, jobs, hooks. É muita infraestrutura.</p>
<p>O que eu descobri nos últimos meses é que eu consigo construir bots customizados pra tarefas específicas muito mais rápido. Em 1 dia eu tenho um bot funcionando, focado no que eu preciso, sem carregar o peso de um framework genérico. Foi o que eu fiz com o <a href="/2026/02/20/discord-como-admin-panel-bastidores-do-the-m-akita-chronicles/">Marvin</a> no projeto da newsletter, por exemplo. Um bot de Discord feito sob medida, que faz exatamente o que eu preciso e nada mais.</p>
<p>Um gateway genérico como o FrankClaw faz mais sentido pra quem quer uma interface unificada entre vários canais de chat e vários modelos de IA sem programar. Se esse é o seu caso, experimenta. Se você é desenvolvedor e sabe o que quer, talvez um bot customizado te sirva melhor. Fica a seu critério.</p>
<p>O <a href="https://github.com/akitaonrails/frankclaw"target="_blank" rel="noopener">repositório está aqui</a>. AGPL-3.0.</p>
]]></content:encoded><category>frank</category><category>llms</category><category>inteligencia-artificial</category><category>rust</category></item><item><title>ai-jail: Sandbox para Agentes de IA — De Shell Script a Ferramenta Real</title><link>https://akitaonrails.github.io/2026/03/01/ai-jail-sandbox-para-agentes-de-ia-de-shell-script-a-ferramenta-real/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/03/01/ai-jail-sandbox-para-agentes-de-ia-de-shell-script-a-ferramenta-real/</guid><pubDate>Sun, 01 Mar 2026 14:00:00 -0300</pubDate><description>&lt;p&gt;Este post é um follow-up direto do &lt;a href="https://akitaonrails.github.io/2026/01/10/ai-agents-garantindo-a-protecao-do-seu-sistema/"&gt;AI Agents: Garantindo a Proteção do seu Sistema&lt;/a&gt;, onde eu mostrei como usar bubblewrap pra criar um jail manual pros seus agentes de IA. Se não leu, leia antes de continuar.&lt;/p&gt;
&lt;p&gt;&amp;ndash;&lt;/p&gt;
&lt;p&gt;Em janeiro publiquei um shell script de ~170 linhas que montava um sandbox com bubblewrap pra rodar Claude Code, OpenCode, Crush e qualquer outro agente de IA. Funcionava. Resolvia o problema. Mas era um script bash jogado em &lt;code&gt;~/.local/bin/&lt;/code&gt; que você tinha que copiar, colar, e rezar pra não precisar customizar demais.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post é um follow-up direto do <a href="/2026/01/10/ai-agents-garantindo-a-protecao-do-seu-sistema/">AI Agents: Garantindo a Proteção do seu Sistema</a>, onde eu mostrei como usar bubblewrap pra criar um jail manual pros seus agentes de IA. Se não leu, leia antes de continuar.</p>
<p>&ndash;</p>
<p>Em janeiro publiquei um shell script de ~170 linhas que montava um sandbox com bubblewrap pra rodar Claude Code, OpenCode, Crush e qualquer outro agente de IA. Funcionava. Resolvia o problema. Mas era um script bash jogado em <code>~/.local/bin/</code> que você tinha que copiar, colar, e rezar pra não precisar customizar demais.</p>
<p>Dois meses usando aquele script todo dia me mostraram os limites. Queria configuração por projeto. Queria suporte a macOS pros devs do meu time. Queria parar de editar arrays em bash toda vez que precisava de um diretório extra. E queria algo que alguém pudesse instalar com <code>brew install</code> ou <code>cargo install</code> em 10 segundos, sem ler 170 linhas de script.</p>
<p>Resultado: <a href="https://github.com/akitaonrails/ai-jail"target="_blank" rel="noopener">ai-jail</a>. Uma ferramenta em Rust, ~880KB, 4 dependências, 124 testes, que faz exatamente o que aquele script fazia e mais. Vou explicar o que mudou e por que você deveria estar usando.</p>
<h2>O Problema (de novo, pra quem pulou o post anterior)<span class="hx:absolute hx:-mt-20" id="o-problema-de-novo-pra-quem-pulou-o-post-anterior"></span>
    <a href="#o-problema-de-novo-pra-quem-pulou-o-post-anterior" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agentes de IA pra programação precisam de acesso ao seu filesystem. Precisam rodar compiladores, linters, grep, ls, make, cargo, npm. O mínimo pra serem úteis. O problema é que junto com esse acesso vem a capacidade de ler <code>~/.aws/credentials</code>, exportar suas chaves SSH, ou rodar um <code>rm -rf</code> fora do diretório do projeto.</p>
<p>Não é paranoia. Supply-chain attacks são reais. Toda hora uma lib de NPM, PyPI, ou RubyGems é comprometida. Se o agente rodar <code>npm install</code> e um post-install script malicioso tentar exfiltrar seus dados, a única coisa entre o atacante e suas credenciais é o que você montou de barreira antes.</p>
<p>A resposta é um sandbox. Especificamente, um que permita o agente trabalhar no diretório do projeto com as ferramentas que precisa, mas que torne invisível todo o resto do sistema.</p>
<h2>De Script pra Ferramenta<span class="hx:absolute hx:-mt-20" id="de-script-pra-ferramenta"></span>
    <a href="#de-script-pra-ferramenta" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O shell script do post anterior já resolvia isso com bubblewrap. O ai-jail resolve o mesmo problema, mas trata as limitações que dois meses de uso diário revelaram:</p>
<table>
  <thead>
      <tr>
          <th>Shell script</th>
          <th>ai-jail</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Configuração editando arrays bash</td>
          <td>Arquivo <code>.ai-jail</code> TOML por projeto</td>
      </tr>
      <tr>
          <td>Só Linux</td>
          <td>Linux + macOS</td>
      </tr>
      <tr>
          <td>GPU/Docker/Display hardcoded</td>
          <td>Auto-detecção com flags pra desligar</td>
      </tr>
      <tr>
          <td>Sem dry-run</td>
          <td><code>--dry-run --verbose</code> mostra tudo</td>
      </tr>
      <tr>
          <td>Sem lockdown</td>
          <td><code>--lockdown</code> pra modo paranóico</td>
      </tr>
      <tr>
          <td>Copiar/colar pra instalar</td>
          <td><code>brew install</code>, <code>cargo install</code>, <code>mise</code></td>
      </tr>
      <tr>
          <td>Sem bootstrap</td>
          <td><code>--bootstrap</code> gera configs de permissão pro Claude/Codex/OpenCode</td>
      </tr>
  </tbody>
</table>
<p>A lógica core é a mesma: bubblewrap cria namespaces isolados de PID, UTS, IPC, monta o <code>$HOME</code> como tmpfs efêmero, e só monta o diretório do projeto com escrita. A diferença é que agora tudo isso é configurável sem editar código.</p>
<h2>Instalação<span class="hx:absolute hx:-mt-20" id="instalação"></span>
    <a href="#instala%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quatro formas:</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"># Homebrew (macOS e Linux)</span>
</span></span><span class="line"><span class="cl">brew tap akitaonrails/tap <span class="o">&amp;&amp;</span> brew install ai-jail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Cargo</span>
</span></span><span class="line"><span class="cl">cargo install ai-jail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Mise</span>
</span></span><span class="line"><span class="cl">mise use -g ubi:akitaonrails/ai-jail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Binário direto do GitHub Releases</span>
</span></span><span class="line"><span class="cl">curl -fsSL https://github.com/akitaonrails/ai-jail/releases/latest/download/ai-jail-linux-x86_64.tar.gz <span class="p">|</span> tar xz
</span></span><span class="line"><span class="cl">sudo mv ai-jail /usr/local/bin/</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>No Linux, bubblewrap precisa estar instalado separadamente: <code>pacman -S bubblewrap</code> (Arch), <code>apt install bubblewrap</code> (Debian/Ubuntu), <code>dnf install bubblewrap</code> (Fedora). No macOS não precisa de nada extra.</p>
<h2>Uso Básico<span class="hx:absolute hx:-mt-20" id="uso-básico"></span>
    <a href="#uso-b%c3%a1sico" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><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="nb">cd</span> ~/Projects/meu-app
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Roda Claude Code dentro do sandbox</span>
</span></span><span class="line"><span class="cl">ai-jail claude
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Roda Codex</span>
</span></span><span class="line"><span class="cl">ai-jail codex
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Roda OpenCode</span>
</span></span><span class="line"><span class="cl">ai-jail opencode
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Bash puro pra debug</span>
</span></span><span class="line"><span class="cl">ai-jail bash
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Qualquer comando</span>
</span></span><span class="line"><span class="cl">ai-jail -- python script.py</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>Na primeira execução, ele cria um arquivo <code>.ai-jail</code> no diretório do projeto:</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"># ai-jail sandbox configuration</span>
</span></span><span class="line"><span class="cl"><span class="c"># Edit freely. Regenerate with: ai-jail --clean --init</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">command</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;claude&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">rw_maps</span> <span class="p">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="nx">ro_maps</span> <span class="p">=</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>Esse arquivo é commitável no repo. Quando outro dev do seu time clona o projeto e roda <code>ai-jail</code>, a mesma configuração se aplica.</p>
<p>Se quiser adicionar diretórios extras, pode fazer pela CLI ou direto no TOML:</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"># Diretório extra com escrita</span>
</span></span><span class="line"><span class="cl">ai-jail --rw-map ~/Projects/shared-lib claude
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Diretório extra somente leitura</span>
</span></span><span class="line"><span class="cl">ai-jail --map /opt/datasets claude</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>Quer ver o que o sandbox vai fazer sem executar nada?</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">ai-jail --dry-run --verbose claude</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 mostra cada mount point, cada flag de isolamento, o comando completo do bubblewrap. Sem surpresas.</p>
<h2>Por Que Bubblewrap no Linux<span class="hx:absolute hx:-mt-20" id="por-que-bubblewrap-no-linux"></span>
    <a href="#por-que-bubblewrap-no-linux" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Avaliei as alternativas antes de decidir. O <a href="https://github.com/akitaonrails/ai-jail/blob/master/docs/sandbox-alternatives.md"target="_blank" rel="noopener">documento completo de análise</a> tá no repositório, mas o resumo é:</p>
<p>Bubblewrap (bwrap) é o sandbox que o Flatpak usa pra isolar todo app desktop. ~50KB de binário, ~4000 linhas de C, mantido pelo time do GNOME. Roda sem root usando <code>CLONE_NEWUSER</code> pra criar namespaces sem privilégio elevado. Tá empacotado em toda distro Linux relevante e testado em escala por milhões de instalações Flatpak.</p>
<p>Considerei e descartei as alternativas. Firejail requer setuid root, e confiar em setuid pra proteger contra agentes que já rodam no seu sistema é contraditório. nsjail e minijail são projetados pra ambientes de produção (Google os usa internamente), complexos demais pra workstation de dev. systemd-nspawn requer root e é pra containers de sistema, não pra isolar um processo.</p>
<p>Landlock é um caso diferente. Ele não substitui bubblewrap — não tem nada a ver com namespaces ou mount isolation. Mas complementa. Landlock é um LSM (Linux Security Module) que controla acesso no nível do VFS, independente dos mount namespaces. Isso fecha vetores que o bwrap sozinho não cobre: rotas de escape pelo <code>/proc</code>, tricks com symlinks dentro de mounts permitidos, e serve como seguro contra bugs no próprio mecanismo de namespaces. A partir da v0.4.0, o ai-jail aplica Landlock automaticamente em kernels 5.13+ como defense-in-depth. Usa ABI V3 (Linux 6.2+) com degradação graceful pra V1 em kernels mais antigos, e vira no-op silencioso se o kernel não suportar. Se causar problema com alguma ferramenta específica, <code>--no-landlock</code> desativa.</p>
<p>Bubblewrap acerta no ponto exato: isolamento real sem root, em toda distro, e simples o suficiente pra wrappear num binário de 880KB.</p>
<h2>O Que o Sandbox Faz no Linux<span class="hx:absolute hx:-mt-20" id="o-que-o-sandbox-faz-no-linux"></span>
    <a href="#o-que-o-sandbox-faz-no-linux" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando você roda <code>ai-jail claude</code>, o seguinte acontece:</p>
<p>O agente roda em namespaces isolados de PID, UTS e IPC, com hostname <code>ai-sandbox</code>, e morre automaticamente se o pai morrer (<code>--die-with-parent</code>).</p>
<p>O filesystem é montado numa sequência específica (bubblewrap é order-dependent). <code>/usr</code>, <code>/etc</code>, <code>/opt</code>, <code>/sys</code> entram read-only pras ferramentas do sistema. <code>/dev</code> e <code>/proc</code> montados pra acesso a devices e processos. <code>/tmp</code> e <code>/run</code> como tmpfs frescos. GPUs auto-detectadas (<code>/dev/nvidia*</code>, <code>/dev/dri</code>). Docker socket, X11/Wayland, <code>/dev/shm</code>, tudo auto-detectado e montado se existir.</p>
<p>A parte mais importante é o tratamento do home directory. <code>$HOME</code> é montado como tmpfs vazio. Depois, seletivamente, dotfiles são layered por cima. <code>.gnupg</code>, <code>.aws</code>, <code>.ssh</code>, <code>.mozilla</code>, <code>.sparrow</code> nunca são montados (dados sensíveis). <code>.claude</code>, <code>.crush</code>, <code>.codex</code>, <code>.aider</code>, <code>.config</code>, <code>.cargo</code>, <code>.cache</code>, <code>.docker</code> entram como read-write porque os agentes precisam escrever aqui. Todo o resto entra read-only. Dentro de <code>~/.config</code>, subdiretórios de browser são escondidos atrás de tmpfs: <code>BraveSoftware</code>, <code>Bitwarden</code>. Mesma coisa em <code>~/.cache</code>: <code>BraveSoftware</code>, <code>chromium</code>, <code>spotify</code>, <code>nvidia</code>. O agente não consegue nem ver que esses diretórios existem.</p>
<p>O diretório corrente do projeto é o único lugar com permissão de escrita (além dos dotdirs de ferramentas). O agente modifica o código, mas não toca no resto.</p>
<h2>macOS: sandbox-exec<span class="hx:absolute hx:-mt-20" id="macos-sandbox-exec"></span>
    <a href="#macos-sandbox-exec" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No macOS, o backend é o <code>sandbox-exec</code> com profiles SBPL (Sandbox Profile Language). É uma API legada da Apple, depreciada oficialmente mas sem substituto público. Funciona hoje, mas a Apple pode remover no futuro.</p>
<p>O ai-jail gera um profile SBPL em runtime que replica a mesma lógica do Linux:</p>
<ul>
<li>Default deny em tudo</li>
<li>Permite operações de processo (exec, fork, signal)</li>
<li>Permite rede (exceto em lockdown)</li>
<li>Permite leitura global, nega paths sensíveis (<code>.gnupg</code>, <code>.aws</code>, <code>.ssh</code>, <code>~/Library/Keychains</code>, <code>~/Library/Mail</code>)</li>
<li>Permite escrita só em diretório do projeto, dotfiles de ferramentas, e <code>/tmp</code></li>
</ul>
<p>As limitações são reais. GPU (Metal) e Display (Cocoa) são system-level no macOS, sandbox-exec não consegue restringir. As flags <code>--no-gpu</code> e <code>--no-display</code> simplesmente não têm efeito no macOS. A paridade entre plataformas é aproximada, não exata.</p>
<p>Mesmo com essas limitações, é melhor do que rodar o agente completamente aberto. O sandbox-exec protege contra acesso a filesystem sensível e, em lockdown, remove permissões de escrita e rede.</p>
<h2>Windows: Não Suportado (E Provavelmente Nunca Será)<span class="hx:absolute hx:-mt-20" id="windows-não-suportado-e-provavelmente-nunca-será"></span>
    <a href="#windows-n%c3%a3o-suportado-e-provavelmente-nunca-ser%c3%a1" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Não é falta de vontade, é falta de primitivas. Windows não tem equivalente userspace a Linux namespaces. Não tem mount API como bubblewrap. AppContainers existem mas usam um modelo de segurança completamente diferente, requerem privilégios de admin, e mapear a funcionalidade do bwrap pra AppContainers seria efetivamente escrever outro projeto do zero.</p>
<p>A solução pra Windows é WSL 2:</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"># No WSL 2 (kernel Linux real)</span>
</span></span><span class="line"><span class="cl">sudo apt install bubblewrap
</span></span><span class="line"><span class="cl">cargo install ai-jail
</span></span><span class="line"><span class="cl"><span class="c1"># ou: mise use -g ubi:akitaonrails/ai-jail</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /mnt/c/Users/voce/Projects/meu-app
</span></span><span class="line"><span class="cl">ai-jail claude</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>WSL 2 roda um kernel Linux de verdade. Bubblewrap funciona normalmente. Os arquivos do Windows ficam acessíveis em <code>/mnt/c/</code>. Performance de I/O é mais lenta pelo mount de 9p, mas funciona. Pra projetos grandes, clonar dentro de <code>~/Projects/</code> no lado Linux melhora a performance consideravelmente.</p>
<h2>Lockdown Mode<span class="hx:absolute hx:-mt-20" id="lockdown-mode"></span>
    <a href="#lockdown-mode" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra workloads que você realmente não confia, existe <code>--lockdown</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-bash" data-lang="bash"><span class="line"><span class="cl">ai-jail --lockdown bash</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>Lockdown faz tudo que o modo normal faz, mas vai além. O projeto é montado read-only (não read-write). GPU, Docker, Display e mise são desabilitados. Flags <code>--rw-map</code> e <code>--map</code> são ignoradas. <code>$HOME</code> vira tmpfs puro, sem dotfiles do host. No Linux, a rede é cortada com <code>--unshare-net</code> e o ambiente é limpo com <code>--clearenv</code>. No macOS, as variáveis de ambiente são limpas e as regras de escrita e rede são removidas do SBPL.</p>
<p>É o sandbox mais restritivo que dá pra fazer sem usar uma VM. Útil pra auditar código de terceiros ou rodar agentes em projetos que você não conhece.</p>
<h2>Bootstrap: Configuração Automática de Permissões<span class="hx:absolute hx:-mt-20" id="bootstrap-configuração-automática-de-permissões"></span>
    <a href="#bootstrap-configura%c3%a7%c3%a3o-autom%c3%a1tica-de-permiss%c3%b5es" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><code>ai-jail --bootstrap</code> gera configurações de permissão pras ferramentas que você usa:</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">ai-jail --bootstrap</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>Pro <strong>Claude Code</strong>, gera <code>~/.claude/settings.json</code> com listas de allow/deny/ask. Permite git status, diff, log, ls, grep, cargo, npm, python, docker compose. Bloqueia rm -rf, sudo, chmod 777, git push &ndash;force. Pergunta antes de git push, rm, docker run.</p>
<p>Pro <strong>Codex</strong>, gera <code>~/.codex/config.toml</code> com <code>approval_policy = &quot;on-request&quot;</code>.</p>
<p>Pro <strong>OpenCode</strong>, gera <code>~/.config/opencode/opencode.json</code> com permissões de bash, edit, write.</p>
<p>Antes de sobrescrever qualquer arquivo, faz backup automático (<code>settings.json.bak</code>). E rejeita operações se o alvo for um symlink, pra evitar path traversal.</p>
<p>É exatamente o conteúdo que eu coloquei manualmente no <a href="/2026/01/10/ai-agents-garantindo-a-protecao-do-seu-sistema/">post anterior</a>, mas automatizado e testado.</p>
<h2>Mas o Claude Code Já Tem Sandbox Próprio<span class="hx:absolute hx:-mt-20" id="mas-o-claude-code-já-tem-sandbox-próprio"></span>
    <a href="#mas-o-claude-code-j%c3%a1-tem-sandbox-pr%c3%b3prio" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem. Desde outubro de 2025, o Claude Code oferece um sandbox runtime via o comando <code>/sandbox</code>. E adivinhe o que ele usa por baixo? Bubblewrap no Linux e sandbox-exec no macOS. O mesmo stack.</p>
<p>Mas as diferenças importam.</p>
<p>ai-jail é agnóstico de ferramenta. Funciona com Claude, Codex, OpenCode, Crush, e qualquer comando. O sandbox do Claude só protege o Claude. Se amanhã você trocar de agente, o ai-jail continua funcionando igual.</p>
<p>O ponto que mais me incomoda é o escape hatch. Quando um comando falha por restrição de sandbox, o Claude pode tentar novamente com <code>dangerouslyDisableSandbox</code>, caindo de volta no fluxo normal de permissões. Dá pra desabilitar isso (<code>&quot;allowUnsandboxedCommands&quot;: false</code>), mas é opt-out, não opt-in. No ai-jail, não existe escape hatch. O processo roda dentro do bwrap ou do sandbox-exec, ponto. Não tem como o agente decidir sozinho sair do sandbox.</p>
<p>Outra diferença prática: o <code>.ai-jail</code> vive no diretório do projeto e pode ser commitado no repositório. Qualquer dev que clona o projeto herda a mesma política de sandbox. O sandbox do Claude depende de <code>settings.json</code> global.</p>
<p>Quando roda dentro de Docker, o sandbox do Claude cai pra um modo <code>enableWeakerNestedSandbox</code> que, nas palavras da própria documentação, <em>&ldquo;considerably weakens security&rdquo;</em>. O ai-jail não foi projetado pra rodar dentro de Docker (ele roda diretamente na workstation do dev), então esse problema não existe.</p>
<p>Sobre rede: o sandbox do Claude roteia tráfego por um proxy e permite/bloqueia por domínio. ai-jail no modo normal herda a rede do host; no lockdown, corta a rede inteira com <code>--unshare-net</code>. A abordagem do Claude é mais granular; a do ai-jail é mais simples e mais difícil de contornar.</p>
<p>Os dois não são mutuamente exclusivos. Dá pra rodar o sandbox do Claude dentro do ai-jail. O ai-jail cuida do isolamento de filesystem; o sandbox do Claude adiciona filtragem de rede por domínio. Layers de segurança se somam.</p>
<h2>Por Que Não Usar &ndash;dangerously-skip-permissions Sem Jail<span class="hx:absolute hx:-mt-20" id="por-que-não-usar-dangerously-skip-permissions-sem-jail"></span>
    <a href="#por-que-n%c3%a3o-usar-dangerously-skip-permissions-sem-jail" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vou ser direto: se você roda Claude Code com <code>--dangerously-skip-permissions</code> sem nenhum sandbox, tá confiando cegamente que a LLM nunca vai executar algo destrutivo. E tá confiando que nenhuma dependência do seu projeto foi comprometida num supply-chain attack.</p>
<p>Toda flag <code>--dangerously</code> tem esse nome por um motivo. O Claude Code é explícito: esse modo existe pra CI/CD e automação em ambientes já isolados (containers, VMs descartáveis). Não pra sua workstation pessoal com <code>~/.aws/credentials</code>, <code>~/.gnupg/</code>, SSH keys, e o banco de senhas do seu browser.</p>
<p>Com ai-jail, o agente tem autonomia total dentro do sandbox. Faz o que quiser no diretório do projeto, usa as ferramentas que precisa, e não consegue acessar nada fora do que foi explicitamente permitido.</p>
<h2>ai-jail + Git: A Rede de Segurança que Você Já Tem<span class="hx:absolute hx:-mt-20" id="ai-jail--git-a-rede-de-segurança-que-você-já-tem"></span>
    <a href="#ai-jail--git-a-rede-de-seguran%c3%a7a-que-voc%c3%aa-j%c3%a1-tem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem uma coisa que eu não mencionei ainda e que muda o cálculo de risco: se o seu projeto tá num repositório Git, com remote no GitHub/GitLab, e o agente não tem permissão de <code>git push</code>, o dano que ele consegue causar é limitado ao diretório local.</p>
<p>Pense assim. O pior cenário dentro do ai-jail é o agente corromper todos os arquivos do projeto. Irritante? Sim. Catastrófico? Não. Você faz <code>git checkout .</code> e volta ao último commit. Se ele corromper o <code>.git</code> de alguma forma (improvável, mas possível), você deleta o diretório e clona de novo. O remote não foi tocado.</p>
<p>Por isso o <code>--bootstrap</code> do ai-jail coloca <code>git push</code> na lista de &ldquo;ask&rdquo; (perguntar antes), não na lista de &ldquo;allow&rdquo;. E <code>git push --force</code> vai direto pra &ldquo;deny&rdquo;. O agente pode commitar à vontade localmente, pode criar branches, pode fazer rebase. Nada disso afeta o remote. Quando chega na hora de push, você revisa o que ele fez e decide se vai pro ar.</p>
<p>Essa combinação, sandbox pra filesystem + Git pro código + push manual, já te dá um nível de segurança muito razoável pro dia a dia. O ai-jail protege seus dados pessoais e o sistema. O Git protege seu código. E a decisão de publicar continua sendo sua.</p>
<p>Se quiser ir além, as próximas duas seções cobrem camadas adicionais.</p>
<h2>ai-jail vs Dev Containers<span class="hx:absolute hx:-mt-20" id="ai-jail-vs-dev-containers"></span>
    <a href="#ai-jail-vs-dev-containers" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Desde que escrevi o ai-jail, a pergunta mais frequente é: <em>&ldquo;por que não usar Dev Containers?&rdquo;</em>. A resposta curta é que um não substitui o outro. Resolvem problemas diferentes.</p>
<p>Dev Containers (a spec do <a href="https://containers.dev"target="_blank" rel="noopener">containers.dev</a>) define um ambiente de desenvolvimento completo num <code>devcontainer.json</code>. Você especifica imagem base, ferramentas, extensões do VS Code, variáveis de ambiente, e o editor monta tudo pra você num container Docker. O Docker também lançou recentemente os Docker Sandboxes, que vão além e rodam cada agente numa microVM com Firecracker, com isolamento de hardware.</p>
<p>ai-jail não faz nada disso. Ele não define ambiente. Não instala ferramentas. Não roda imagem Docker. Ele pega o ambiente que já existe na sua máquina e restringe o que o processo pode acessar.</p>
<p>Na prática, a diferença é:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>Dev Container</th>
          <th>ai-jail</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>O que faz</td>
          <td>Define e provisiona um ambiente isolado completo</td>
          <td>Restringe acesso do processo ao filesystem existente</td>
      </tr>
      <tr>
          <td>Setup</td>
          <td><code>devcontainer.json</code> + Docker</td>
          <td><code>.ai-jail</code> TOML + bubblewrap</td>
      </tr>
      <tr>
          <td>Startup</td>
          <td>Segundos (pull de imagem, build de container)</td>
          <td>Milissegundos (fork + exec do bwrap)</td>
      </tr>
      <tr>
          <td>Ferramentas</td>
          <td>As que você coloca na imagem</td>
          <td>As que já estão instaladas na sua máquina</td>
      </tr>
      <tr>
          <td>GPU</td>
          <td>Requer configuração de NVIDIA Container Toolkit</td>
          <td>Auto-detecta <code>/dev/nvidia*</code> e <code>/dev/dri</code></td>
      </tr>
      <tr>
          <td>Daemon</td>
          <td>Requer Docker daemon rodando</td>
          <td>Nada além do bwrap</td>
      </tr>
      <tr>
          <td>Reprodutibilidade</td>
          <td>Alta (imagem fixa)</td>
          <td>Depende do que tá instalado no host</td>
      </tr>
      <tr>
          <td>Isolamento de rede</td>
          <td>Docker Sandboxes: firewall por domínio</td>
          <td>Lockdown: corta tudo com <code>--unshare-net</code></td>
      </tr>
  </tbody>
</table>
<p>Dev Container faz mais sentido quando você precisa que todo o time tenha exatamente o mesmo ambiente, ou quando o projeto tem dependências que ninguém quer instalar no host, ou pra rodar agentes não-interativos em CI/CD. Docker Sandboxes com microVM são o isolamento mais forte que existe fora de uma VM dedicada.</p>
<p>ai-jail faz mais sentido quando você já tem o ambiente configurado e quer startup instantâneo sem Docker daemon. Ou quando usa ferramentas que são chatas de rodar dentro de container (CUDA, Wayland, mise). Ou simplesmente quando quer a mesma proteção pra qualquer agente, não só os que têm integração com devcontainer.</p>
<p>E dá pra combinar. Eu conheço gente que roda ai-jail dentro de um Dev Container pra ter reprodutibilidade de ambiente + restrição de filesystem. Layers de segurança se somam.</p>
<h2>Sistemas Operacionais Imutáveis: A Última Camada<span class="hx:absolute hx:-mt-20" id="sistemas-operacionais-imutáveis-a-última-camada"></span>
    <a href="#sistemas-operacionais-imut%c3%a1veis-a-%c3%baltima-camada" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se você quer levar a segurança a sério, a terceira camada é o sistema operacional em si.</p>
<p>Sistemas imutáveis como <a href="https://fedoraproject.org/atomic-desktops/silverblue/"target="_blank" rel="noopener">Fedora Silverblue</a>, <a href="https://nixos.org/"target="_blank" rel="noopener">NixOS</a>, e <a href="https://aeondesktop.github.io/"target="_blank" rel="noopener">openSUSE Aeon</a> têm o filesystem raiz read-only. O sistema base não pode ser modificado por nenhum processo, nem com root. Atualizações são atômicas: ou aplicam completamente ou não aplicam. E se algo der errado, você faz rollback pra imagem anterior em um reboot.</p>
<p>Na prática, isso significa que mesmo se um agente de IA escapasse do sandbox (exploiting uma vulnerabilidade no kernel, por exemplo), ele não conseguiria modificar o sistema de forma persistente. No próximo reboot, o sistema volta ao estado declarado. No NixOS, o sistema inteiro é definido por um arquivo de configuração (<code>configuration.nix</code>). No Silverblue, o base é uma imagem OSTree que recebe updates atômicos via <code>rpm-ostree</code>.</p>
<p>Pra desenvolvedores, a pegada é: suas ferramentas de dev rodam em containers (Toolbox/Distrobox no Silverblue, <code>nix-shell</code> no NixOS). O sistema base fica intocado. Apps desktop vêm via Flatpak, que já roda em sandbox. O resultado é que a superfície de ataque do host é mínima.</p>
<p>Fedora Silverblue é provavelmente a entrada mais acessível. Já é Fedora por baixo, com GNOME, funciona com hardware que Fedora suporta, e o Toolbox te dá um Fedora Server containerizado onde você instala o que quiser sem tocar no host. NixOS é mais poderoso (reprodutibilidade total, rollback declarativo), mas a curva de aprendizado é real.</p>
<p>A combinação completa fica assim: o OS imutável cuida do sistema (filesystem read-only, updates atômicos, rollback em um reboot). O ai-jail cuida da sessão (namespace isolado, home efêmero, dados sensíveis invisíveis). E o Git cuida do código (remote intocado enquanto o agente não tiver push).</p>
<p>Nenhuma dessas camadas é perfeita sozinha. Mas o ataque que fura as três ao mesmo tempo — escapar do namespace, persistir num filesystem read-only, e corromper um remote Git — é um cenário que eu ficaria confortável chamando de improvável.</p>
<p>O melhor é que nenhuma exige mudar como você trabalha. O ai-jail é um comando antes do seu agente. Git você já usa. E um OS imutável é uma instalação, não uma mudança de workflow.</p>
<h2>Detalhes Técnicos (Pra Quem Se Importa)<span class="hx:absolute hx:-mt-20" id="detalhes-técnicos-pra-quem-se-importa"></span>
    <a href="#detalhes-t%c3%a9cnicos-pra-quem-se-importa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Escrito em Rust com 124 testes e 4 dependências: <code>lexopt</code> (parsing de CLI sem clap), <code>serde</code> + <code>toml</code> (config), <code>nix</code> (syscalls Unix). Sem runtime async, sem framework de cores (ANSI direto), binary de ~880KB com LTO e strip.</p>
<p>O signal handling é feito corretamente: SIGINT, SIGTERM, e SIGHUP são forwarded pro processo filho. O handler só chama <code>libc::kill</code>, que é async-signal-safe. O reaping do processo usa <code>waitpid</code> em loop com retry em EINTR.</p>
<p>Arquivos temporários (como o <code>/etc/hosts</code> customizado que o sandbox monta) usam RAII: um <code>SandboxGuard</code> que implementa <code>Drop</code> em Rust. Se o processo pai morrer por qualquer razão, o cleanup acontece.</p>
<p>A compatibilidade de configuração é garantida por política de desenvolvimento: nunca remover campos, nunca renomear, campos novos sempre com <code>#[serde(default)]</code>, campos desconhecidos são silenciosamente ignorados. Testes de regressão pra formatos antigos de <code>.ai-jail</code> garantem que atualizar o binário nunca quebra configs existentes. São 32 testes só de config.</p>
<h2>Roadmap<span class="hx:absolute hx:-mt-20" id="roadmap"></span>
    <a href="#roadmap" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O que falta:</p>
<ul>
<li>Mais backends de ferramentas no bootstrap: Aider, Cursor, Windsurf. Conforme mais agentes padronizarem arquivos de configuração de permissão.</li>
<li>Profile sharing pra monorepos, pra não ter que configurar cada serviço separadamente.</li>
</ul>
<h2>Instalação e Primeiro Uso (Resumo Rápido)<span class="hx:absolute hx:-mt-20" id="instalação-e-primeiro-uso-resumo-rápido"></span>
    <a href="#instala%c3%a7%c3%a3o-e-primeiro-uso-resumo-r%c3%a1pido" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><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"># 1. Instalar</span>
</span></span><span class="line"><span class="cl">brew tap akitaonrails/tap <span class="o">&amp;&amp;</span> brew install ai-jail
</span></span><span class="line"><span class="cl"><span class="c1"># ou: cargo install ai-jail</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. No Linux, instalar bubblewrap</span>
</span></span><span class="line"><span class="cl">sudo pacman -S bubblewrap  <span class="c1"># Arch</span>
</span></span><span class="line"><span class="cl"><span class="c1"># sudo apt install bubblewrap  # Debian/Ubuntu</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 3. Entrar no projeto e rodar</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/Projects/meu-app
</span></span><span class="line"><span class="cl">ai-jail claude
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 4. (Opcional) Gerar configs de permissão</span>
</span></span><span class="line"><span class="cl">ai-jail --bootstrap
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 5. (Opcional) Ver o que o sandbox faz</span>
</span></span><span class="line"><span class="cl">ai-jail --dry-run --verbose claude</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 <code>.ai-jail</code> criado pode ser commitado no seu repo. A partir daí, qualquer dev que clonar o projeto roda <code>ai-jail claude</code> e tem o mesmo sandbox.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O shell script de janeiro resolveu o problema. O ai-jail resolve o problema direito. Config por projeto, suporte a macOS, lockdown mode, bootstrap de permissões, dry-run pra auditar, e um binário de 880KB que instala em 10 segundos.</p>
<p>Se você usa agentes de IA pra programar, rode eles num sandbox. A boa vontade da LLM não é garantia de nada, e supply-chain attacks não escolhem vítima. Isolamento de processo é a barreira que funciona.</p>
<p>O projeto é GPL-3.0 e tá no GitHub: <a href="https://github.com/akitaonrails/ai-jail"target="_blank" rel="noopener">github.com/akitaonrails/ai-jail</a></p>
<p>Issues e PRs são bem-vindos.</p>
]]></content:encoded><category>ai-jail</category><category>agentes-de-codigo</category><category>seguranca</category><category>rust</category></item><item><title>[Primeiros Passos] Brincando de Rust + Ruby/FFI</title><link>https://akitaonrails.github.io/2015/06/05/primeiros-passos-brincando-de-rust-ruby-ffi/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/06/05/primeiros-passos-brincando-de-rust-ruby-ffi/</guid><pubDate>Fri, 05 Jun 2015 16:05:00 -0300</pubDate><description>&lt;p&gt;Estou nos meus primeiros dias estudando &lt;a href="http://www.rust-lang.org"target="_blank" rel="noopener"&gt;Rust&lt;/a&gt;, a nova linguagem de sistema criado pela Mozilla. Essa linguagem está no meu radar faz vários meses, principalmente pelo suporte positivo de rubistas influentes como Steve Klabnik e Yehuda Katz.&lt;/p&gt;
&lt;p&gt;Meu interesse é simples. Rust é uma linguagem pequena, mais próximo da categoria de C ou Objective-C do que GoLang, ou Elixir. Uma das coisas que sempre podemos fazer para &amp;ldquo;vitaminar&amp;rdquo; nosso querido Ruby é criar extensions em C. Mas se você já tentou fazer isso, sabe que nem é tão complicado com pequenas coisas à la &amp;ldquo;hello world&amp;rdquo;, mas a coisa pode ficar exponencialmente complicada com muitas dependências e complexidades de toolchains. Então minha intenção em aprender Rust é ver se ela pode ser uma boa alternativa para criar extensions nativas performática facilmente consumíveis via &lt;a href="https://github.com/ffi/ffi/wiki/Why-use-FFI"target="_blank" rel="noopener"&gt;FFI&lt;/a&gt; (Foreign Function Interface).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Estou nos meus primeiros dias estudando <a href="http://www.rust-lang.org"target="_blank" rel="noopener">Rust</a>, a nova linguagem de sistema criado pela Mozilla. Essa linguagem está no meu radar faz vários meses, principalmente pelo suporte positivo de rubistas influentes como Steve Klabnik e Yehuda Katz.</p>
<p>Meu interesse é simples. Rust é uma linguagem pequena, mais próximo da categoria de C ou Objective-C do que GoLang, ou Elixir. Uma das coisas que sempre podemos fazer para &ldquo;vitaminar&rdquo; nosso querido Ruby é criar extensions em C. Mas se você já tentou fazer isso, sabe que nem é tão complicado com pequenas coisas à la &ldquo;hello world&rdquo;, mas a coisa pode ficar exponencialmente complicada com muitas dependências e complexidades de toolchains. Então minha intenção em aprender Rust é ver se ela pode ser uma boa alternativa para criar extensions nativas performática facilmente consumíveis via <a href="https://github.com/ffi/ffi/wiki/Why-use-FFI"target="_blank" rel="noopener">FFI</a> (Foreign Function Interface).</p>
<p>Este artigo é apenas um pequeno exercício que vai além de um simples &ldquo;hello world&rdquo;, que seria absolutamente trivial. Quero fazer uma pequena biblioteca capaz de ler um arquivo de tamanho arbitrário (portanto não pode carregar tudo em memória) e fazer um parse com regular expressions (algo que fazemos comumente).</p>
<p>Para isso estou usando como teste um arquivo de atores de filmes que baixei do <a href="http://www.imdb.com/interfaces"target="_blank" rel="noopener">FTP do IMDB</a>, em particular o arquivo &ldquo;actors.tgz&rdquo; que abre como &ldquo;actors.list&rdquo;. De cara vou dizer que é um dump bem porcaria, cujo texto sequer está em UTF-8. E para os efeitos deste artigo eu fiz uma versão menor com somente as primeira 10 mil linhas dele, que é um mísero arquivo de 515kb (comparado aos 938MB originais). Poderia ser qualquer arquivo, mas aleatoriamente escolhi esse.</p>
<p>Coloquei o projeto <a href="https://github.com/akitaonrails/rust_ruby_exercise_1"target="_blank" rel="noopener">no meu Github como exercício</a> (contribuições são bem vindas, pra melhorar o exemplo). Em Ruby, o código é simplesmente algo 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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">find_actors</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">skip_lines</span><span class="p">,</span> <span class="n">target_movie</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">actors</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl">  <span class="n">actor</span> <span class="o">=</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">  <span class="no">File</span><span class="o">.</span><span class="n">foreach</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span><span class="o">.</span><span class="n">with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="p">,</span> <span class="n">line_num</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="k">next</span> <span class="k">if</span> <span class="n">line_num</span> <span class="o">&lt;</span> <span class="mi">239</span>
</span></span><span class="line"><span class="cl">    <span class="n">line</span><span class="o">.</span><span class="n">encode!</span><span class="p">(</span><span class="n">line</span><span class="o">.</span><span class="n">encoding</span><span class="p">,</span> <span class="s1">&#39;binary&#39;</span><span class="p">,</span> <span class="ss">invalid</span><span class="p">:</span> <span class="ss">:replace</span><span class="p">,</span> <span class="k">undef</span><span class="p">:</span> <span class="ss">:replace</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span> <span class="o">==</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">      <span class="n">actor</span> <span class="o">=</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">      <span class="k">next</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="n">slices</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="sr">/\t/</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">actor_buffer</span> <span class="o">=</span> <span class="n">slices</span><span class="o">.</span><span class="n">first</span>
</span></span><span class="line"><span class="cl">    <span class="n">movie</span>        <span class="o">=</span> <span class="n">slices</span><span class="o">.</span><span class="n">last</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">actor</span><span class="o">.</span><span class="n">nil?</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">actor_buffer</span><span class="o">.</span><span class="n">nil?</span> <span class="o">&amp;&amp;</span> <span class="n">actor_buffer</span> <span class="o">!=</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">      <span class="n">actor</span> <span class="o">=</span> <span class="n">actor_buffer</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">!</span><span class="n">movie</span><span class="o">.</span><span class="n">nil?</span> <span class="o">&amp;&amp;</span> <span class="n">movie</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">target_movie</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">actors</span> <span class="o">&lt;&lt;</span> <span class="n">actor</span> <span class="k">unless</span> <span class="n">actors</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">actor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="n">actors</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>Dá pra melhorar isso, mas é o suficiente para nossos propósitos. Rodando contra o arquivo pequeno de exemplo, o resultado vai ser:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&gt; ruby actors.rb                                                                                                                               running pure Ruby version
145, Lyric
3, Utai
4 Real
4Shore
4Sure
4th Ba5e
4Tune
50 Cent
  0.050000   0.000000   0.050000 (  0.060104)</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>Se você baixar meu código do repositório, primeiro garanta que você tem o Rust instalado. Pra isso basta executar o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>curl -sSf https://static.rust-lang.org/rustup.sh | sh</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>Quando baixar meu código, vai ver que ele tem os arquivos Cargo.toml e Cargo.lock. Eles são semelhantes ao nosso Gemfile/Rakefile e Gemfile.lock. No Ruby controlamos nossas tarefas com Rake, as gems com Bundler (que lê as versões exatas do Gemfile.lock) e normalmente baixamos dependências que são Rubygems do Rubygems.org.</p>
<p>No caso do Rust, ele controla tanto tarefas (build, teste) e declaração de dependências via o arquivo Cargo.toml (<a href="https://github.com/toml-lang/toml"target="_blank" rel="noopener">TOML</a> sendo uma alternativa mais moderna a YAML). Em vez de Rake e Bundler temos <a href="http://doc.crates.io/guide.html"target="_blank" rel="noopener">Cargo</a>. Em vez de Rubygems temos Crates. E em vez de Rubygems.org temos <a href="https://crates.io"target="_blank" rel="noopener">Crates.io</a>.</p>
<p>Sendo uma linguagem que compila binários nativos, executamos <tt>cargo build</tt> mas se fizermos diretamente <tt>cargo test</tt> (para rodar testes incluídos no código) ou <tt>cargo run</tt> (para rodar o binário executável que fica no diretório <tt>bin/</tt>) ele vai automaticamente fazer a compilação do que precisa. No nosso caso, como estamos gerando uma biblioteca (que poderia ser um &ldquo;<em>.so&rdquo; para Linux, ou um &ldquo;</em>.dylib&rdquo; pra Mac ou &ldquo;*.dll&rdquo; para Windows), ele compila por padrão no diretório <tt>target/debug</tt> (que vai ser mais lento por ter símbolos pra debug e outros suportes). Para gerar a versão final, precisa executar <tt>cargo build &ndash;release</tt> e rodar como <tt>cargo run &ndash;release</tt> que vai gerar e linkar do binário em <tt>target/release</tt>.</p>
<p>Isso tudo dito, dê uma olhada nos arquivos Cargo.toml, src/main.rs, src/lib.rs. Os arquivos de Ruby estão misturados e são os <tt>actors.rb</tt>, <tt>imdb.rb</tt>. Pra executar a versão Ruby, lembre-se de também rodar <tt>gem install ffi</tt> primeiro (não estou usando Bundler nesse exercício).</p>
<h2>O Código Rust<span class="hx:absolute hx:-mt-20" id="o-código-rust"></span>
    <a href="#o-c%c3%b3digo-rust" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora vamos olhar o código Rust equivalente ao Ruby anterior:</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">use</span> <span class="n">std</span><span class="o">::</span><span class="n">io</span><span class="o">::</span><span class="n">prelude</span><span class="o">::*</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">use</span> <span class="n">std</span><span class="o">::</span><span class="n">io</span><span class="o">::</span><span class="no">BufReader</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">use</span> <span class="n">std</span><span class="o">::</span><span class="n">fs</span><span class="o">::</span><span class="no">File</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">extern</span> <span class="n">crate</span> <span class="n">regex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">use</span> <span class="n">regex</span><span class="o">::</span><span class="no">Regex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">pub</span> <span class="n">fn</span> <span class="n">find_actors</span><span class="p">(</span><span class="ss">filename</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="ss">skip_lines</span><span class="p">:</span> <span class="n">usize</span><span class="p">,</span> <span class="ss">target_movie</span><span class="p">:</span> <span class="nb">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">let</span> <span class="n">file</span> <span class="o">=</span> <span class="no">File</span><span class="o">::</span><span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span><span class="o">.</span><span class="n">unwrap</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">let</span> <span class="n">mut</span> <span class="n">reader</span> <span class="o">=</span> <span class="no">BufReader</span><span class="o">::</span><span class="kp">new</span><span class="p">(</span><span class="o">&amp;</span><span class="n">file</span><span class="p">)</span><span class="o">.</span><span class="n">lines</span><span class="p">()</span><span class="o">.</span><span class="n">skip</span><span class="p">(</span><span class="n">skip_lines</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">let</span> <span class="n">mut</span> <span class="n">actor</span> <span class="o">=</span> <span class="nb">String</span><span class="o">::</span><span class="kp">new</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">let</span> <span class="n">mut</span> <span class="n">actors</span> <span class="p">:</span> <span class="no">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span> <span class="o">=</span> <span class="no">Vec</span><span class="o">::</span><span class="kp">new</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">let</span> <span class="n">regex</span> <span class="o">=</span> <span class="no">Regex</span><span class="o">::</span><span class="kp">new</span><span class="p">(</span><span class="n">r</span><span class="s2">&#34;^(.*?)</span><span class="se">\t</span><span class="s2">+(.*?)$&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">unwrap</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kp">loop</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">let</span> <span class="n">line</span> <span class="o">=</span> <span class="n">match</span> <span class="n">reader</span><span class="o">.</span><span class="n">next</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="no">Some</span><span class="p">(</span><span class="n">line</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">match</span> <span class="n">line</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="no">Ok</span><span class="p">(</span><span class="n">line</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">line</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="no">Err</span><span class="p">(</span><span class="n">_</span><span class="p">)</span>   <span class="o">=&gt;</span> <span class="nb">String</span><span class="o">::</span><span class="kp">new</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="no">None</span> <span class="o">=&gt;</span> <span class="k">break</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">match</span> <span class="n">regex</span><span class="o">.</span><span class="n">captures</span><span class="p">(</span><span class="o">&amp;</span><span class="n">line</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="no">Some</span><span class="p">(</span><span class="n">captures</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">let</span> <span class="n">actor_buffer</span> <span class="o">=</span> <span class="n">captures</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">unwrap</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">                <span class="n">let</span> <span class="n">movie</span>        <span class="o">=</span> <span class="n">captures</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">unwrap</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">actor</span><span class="o">.</span><span class="n">is_empty</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">actor_buffer</span><span class="o">.</span><span class="n">is_empty</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">actor</span> <span class="o">=</span> <span class="n">actor_buffer</span><span class="o">.</span><span class="n">to_string</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="o">!</span><span class="n">movie</span><span class="o">.</span><span class="n">is_empty</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">movie</span><span class="o">.</span><span class="n">contains</span><span class="p">(</span><span class="o">&amp;</span><span class="n">target_movie</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">actors</span><span class="o">.</span><span class="n">contains</span><span class="p">(</span><span class="o">&amp;</span><span class="n">actor</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">actors</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="n">actor</span><span class="o">.</span><span class="n">to_string</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="no">None</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">actor</span> <span class="o">=</span> <span class="nb">String</span><span class="o">::</span><span class="kp">new</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">actors</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</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>Essa função usa coisas do próprio Rust que importamos dos módulos <tt>str::io</tt> e <tt>std::fs</tt> e tem a Crate regex externa. Também exportamos esse módulo com o nome de &ldquo;imdb&rdquo; que é o declaramos no Cargo.toml:</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="p">[</span><span class="l">package]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">name = &#34;actors&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">version = &#34;0.1.0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">authors = [&#34;vagrant&#34;]</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="p">[</span><span class="l">lib]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">name = &#34;imdb&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">path = &#34;src/lib.rs&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">crate-type = [&#34;rlib&#34;, &#34;dylib&#34;]</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="p">[</span><span class="l">dependencies]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">regex = &#34;0.1.8&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="l">libc = &#34;0.1.8&#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>Eu removi um trecho do arquivo que vou explicar na próxima seção, sobre FFI. Por enquanto vamos nos ater a esse código.</p>
<p>A sintaxe em si não deve ser tão assustadora num primeiro momento. As coisas estranhas vão exigir algum estudo.</p>
<ul>
<li>
<p>O Rust tem tipos e eles são declarados. O compilador usa inferência de tipos então não precisa declarar tudo o tempo todo como em Java. Ao contrário de Ruby, Rust não é orientado a &ldquo;objetos&rdquo;. Ele tem structs e funções declaradas pra essas structs. Cada struct pode implementar funções declaradas em <a href="https://doc.rust-lang.org/book/traits.html"target="_blank" rel="noopener">Traits</a>, que funcionam mais ou menos como Interfaces ou Protocolos (em Swift/Obj-C). Leia <a href="http://blog.rust-lang.org/2015/05/11/traits.html"target="_blank" rel="noopener">outro artigo sobre Trait aqui</a>.</p>
</li>
<li>
<p>Existe o conceito de <a href="https://doc.rust-lang.org/book/generics.html"target="_blank" rel="noopener">Generics</a>. Por isso a sintaxe de <tt>Vec<String></tt> que declara um <a href="https://doc.rust-lang.org/book/vectors.html"target="_blank" rel="noopener">Vetor</a> com cada elemento sendo um String. Vetores são muito usados, então estude a respeito, mas é basicamente um Array de tamanho dinâmico (que pode receber novos elementos, possivelmente uma lista ligada).</p>
</li>
<li>
<p>Falando em Strings, existe a struct <a href="https://doc.rust-lang.org/book/strings.html"target="_blank" rel="noopener">String</a> e a primitiva &lsquo;str&rsquo;. Um <tt>&ldquo;hello world&rdquo;</tt> é um &lsquo;str&rsquo; que pode virar um String se fizermos <tt>&ldquo;hello world&rdquo;.to_string()</tt>.</p>
</li>
<li>
<p>Em Rust, todos bindings de variáveis são imutáveis. Para torná-la mutável precisamos declarar explicitamente com <tt>let mut</tt> como no exemplo acima. Leia mais sobre <a href="https://doc.rust-lang.org/book/variable-bindings.html"target="_blank" rel="noopener">Variable Binding</a>. Em particular, isso leva aos conceitos de <a href="https://doc.rust-lang.org/book/ownership.html"target="_blank" rel="noopener">Ownership/Propriedade</a> de uma variável, <a href="https://doc.rust-lang.org/book/references-and-borrowing.html"target="_blank" rel="noopener">Borrowing/Empréstimo</a> de variáveis entre escopos diferentes, como passar para uma função e <a href="https://doc.rust-lang.org/book/lifetimes.html"target="_blank" rel="noopener">Lifetime/Tempo de Vida</a> de uma variável. Vou adiantar que esse é um dos conceitos que pode levar mais tempo para se acostumar.</p>
</li>
</ul>
<p>Rust não tem garbage collector como em Ruby ou Java. Primeiro, porque ele usa primariamente o Stack em vez do Heap. Stack é uma pilha. Toda chamada de função empilha as variáveis que usa em seu espaço. Quando chama uma nova função ele empilha isso acima de si com suas variáveis. Quando a última função retorna, ele pode limpar as variáveis que alocou (que são sua &ldquo;propriedade&rdquo;). Se precisamos passar uma variável de uma função para a função seguinte podemos ou &ldquo;copiar&rdquo; o valor (normvalmente o que se faz com primitivas como i32 ou f64 - que são inteiros de 32bits ou floats de 64-bits, dentre outros tipos primitivos) ou podemos &ldquo;mover a propriedade&rdquo;, por exemplo:</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">let</span> <span class="n">x</span> <span class="o">=</span> <span class="s2">&#34;hello&#34;</span><span class="o">.</span><span class="n">to_string</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">let</span> <span class="n">y</span> <span class="o">=</span> <span class="n">x</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">println!</span><span class="p">(</span><span class="s2">&#34;{}&#34;</span><span class="p">,</span> <span class="n">x</span><span class="p">);</span> <span class="sr">//</span> <span class="n">vai</span> <span class="n">dar</span> <span class="n">pau</span><span class="p">,</span> <span class="n">porque</span> <span class="n">movemos</span> <span class="n">a</span> <span class="n">propriedade</span> <span class="n">de</span> <span class="s2">&#34;x&#34;</span> <span class="n">para</span> <span class="n">o</span> <span class="s2">&#34;y&#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>Ou &ldquo;emprestar&rdquo;. Empréstimos são declarados com &ldquo;&amp;&rdquo; (&rsquo;e&rsquo; comercial) e somente podemos emprestar uma única vez como em <tt>let y = &amp;x</tt>. Sim, essa mecânica vai demorar mais pra se entender se você só conhece linguagens como Ruby ou Javascript. Se você aprendeu Objective-C antes do advento do <a href="http://blog.caelum.com.br/gerenciamento-de-memoria-e-o-arc-no-objective-c/"target="_blank" rel="noopener">ARC</a>, já teve que parar pra pensar nesse tipo de <a href="http://www.akitaonrails.com/2010/11/25/objective-c-entendendo-nsautoreleasepool"target="_blank" rel="noopener">ciclo de vida de retain/copy/release</a>.</p>
<p>Outros artigos <a href="http://nercury.github.io/rust/guide/2015/01/19/ownership.html"target="_blank" rel="noopener">como este</a> ou <a href="http://arthurtw.github.io/2014/11/30/rust-borrow-lifetimes.html"target="_blank" rel="noopener">este</a> podem ajudar. O objetivo dessa mecânica é para justamente evitar possibilidades de leaks de memória, ter desalocação determinística, usar a menor quantidade de memória quanto possível. Tecnicamente, não deveria haver leaks de memória óbvios em Rust. E não ter a lógica de Garbage Collection elimina uma das maiores complexidades que temos em linguagens mais modernas que contam com uma VM, o que facilita o uso do Rust para mais usos de sistema mais de baixo nível.</p>
<ul>
<li>Você vai ver várias chamadas a <tt>unwrap()</tt>, isso pode ser estranho. Então veja esta linha em específico:</li>
</ul>
<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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">let</span> <span class="n">regex</span> <span class="o">=</span> <span class="no">Regex</span><span class="o">::</span><span class="kp">new</span><span class="p">(</span><span class="n">r</span><span class="s2">&#34;^(.*?)</span><span class="se">\t</span><span class="s2">+(.*?)$&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">unwrap</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 é só um jeito mais curto pra:</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">let</span> <span class="n">regex</span> <span class="o">=</span> <span class="n">match</span> <span class="no">Regex</span><span class="o">::</span><span class="kp">new</span><span class="p">(</span><span class="n">r</span><span class="s2">&#34;^(.*?)</span><span class="se">\t</span><span class="s2">+(.*?)$&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="no">Ok</span><span class="p">(</span><span class="n">regex</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">regex</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="no">Err</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">panic!</span><span class="p">(</span><span class="s2">&#34;invalid regex&#34;</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>O <tt>match</tt> é a avaliação de um <a href="https://doc.rust-lang.org/book/patterns.html"target="_blank" rel="noopener">Pattern Matching</a> sobre um tipo chamado core::result::Result que é a alternativa do Rust de evitar retornar códigos de erro (como em C) ou usar um sistema de exceptions (como em Ruby mesmo). Nesse caso a criação de uma struct de Regex pode dar certo ou errado. Se der certo teremos o resultado voltando como &ldquo;Ok&rdquo;, se der errado voltará como &ldquo;Err(e)&rdquo; e podemos fazer alguma coisa com o erro ou parar tudo como no exemplo, chamando a macro &ldquo;panic!&rdquo;. O método unwrap implementa exatamente essa lógica se não estamos interessados em tratar o erro.</p>
<p>Em conjunto com o conceito de Result, temos <a href="http://hoverbear.org/2014/08/12/option-monads-in-rust/"target="_blank" rel="noopener">Option Monads</a> (implementado como core::option::Option), também conhecidos como tipos &ldquo;Maybe&rdquo; que devolvem &ldquo;Some&rdquo; (algum valor) ou &ldquo;None&rdquo; (nenhum valor), que é a resposta do Rust pra não ter que lidar com Null. Em Ruby, como existe a classe NilClass, podemos criar ferramentas como <a href="http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/"target="_blank" rel="noopener">NullObjects</a> onde um &ldquo;Maybe&rdquo; devolve esse NullObject/None ou o valor em si, &ldquo;Some&rdquo;.</p>
<p>Veja a biblioteca <a href="https://github.com/avdi/naught"target="_blank" rel="noopener">Naught</a> pra entender mais do conceito.</p>
<p>Em resumo isso significa que você vai ver muito códigos com &ldquo;unwrap&rdquo; e &ldquo;match&rdquo; lidando com &ldquo;Ok/Err&rdquo; ou &ldquo;Some/None&rdquo;. Sendo honesto, ainda não estou tão seguro das melhores práticas de quando e como usar isso e definitivamente é um tópico que vou estudar mais.</p>
<p>Veja que tanto no <tt>reader.next()</tt>, que é pegando o próximo elemento o Iterador do Regex quanto o <tt>regex.captures()</tt> que faz o match da Regex contra o string da linha devolvem o tipo &ldquo;Option&rdquo; e usamos &ldquo;match&rdquo; para saber o que fazer. No primeiro caso, se não houver um próximo elemento no iterador, ele devolve &ldquo;None&rdquo; e sabemos que podemos sair (break) do loop. E no segundo caso, se a linha sendo processada não bater com a regex, ele devolve &ldquo;None&rdquo; e sabemos que acabou o bloco do ator corrente, então podemos zerá-la antes de começar o próximo ator.</p>
<ul>
<li>O resto do código depois do &ldquo;captures()&rdquo; é a mesma lógica que em Ruby, de checar linha do arquivo, dividir entre nome do ator e seus filmes, acumular o nome do último ator (porque no formato do arquivo um ator tem vários filmes, um em cada linha), e ir acumulando os nomes no Vetor/Array. No final simplemente pegamos todos os nomes e concatenamos com um &ldquo;\n&rdquo;.</li>
</ul>
<p>Este código não lida com alguns conceitos mais interessantes do Rust como <a href="http://doc.rust-lang.org/book/dining-philosophers.html"target="_blank" rel="noopener">Threads</a>. Recomendo, no mínimo, ler toda a documentação oficial no site do Rust para entender esses e mais dos conceitos importantes.</p>
<h2>Exportando para consumo em FFI<span class="hx:absolute hx:-mt-20" id="exportando-para-consumo-em-ffi"></span>
    <a href="#exportando-para-consumo-em-ffi" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A maioria dos exemplos que achamos primeiro usando o Google, são códigos simples. Estou usando direto a gem FFI em vez de usar a Fiddle, então saibam que existem <a href="http://blog.skylight.io/bending-the-curve-writing-safe-fast-native-gems-with-rust/"target="_blank" rel="noopener">duas maneiras</a>.</p>
<p>Sem mais delongas, no mesmo arquivo <tt>src/lib.rs</tt> eu coloquei o seguinte:</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">extern</span> <span class="n">crate</span> <span class="n">libc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">use</span> <span class="n">libc</span><span class="o">::</span><span class="n">c_char</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">use</span> <span class="n">std</span><span class="o">::</span><span class="n">ffi</span><span class="o">::</span><span class="p">{</span><span class="no">CString</span><span class="p">,</span> <span class="no">CStr</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#[no_mangle]</span>
</span></span><span class="line"><span class="cl"><span class="n">pub</span> <span class="n">extern</span> <span class="s2">&#34;C&#34;</span> <span class="n">fn</span> <span class="n">ffi_find_actors</span><span class="p">(</span><span class="ss">filename_ptr</span><span class="p">:</span> <span class="o">*</span><span class="n">const</span> <span class="n">c_char</span><span class="p">,</span> <span class="ss">skip_lines</span><span class="p">:</span> <span class="n">i32</span><span class="p">,</span> <span class="ss">target_movie_ptr</span><span class="p">:</span> <span class="o">*</span><span class="n">const</span> <span class="n">c_char</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="o">*</span><span class="n">const</span> <span class="n">c_char</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">let</span> <span class="n">filename</span> <span class="o">=</span> <span class="n">unsafe</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="no">CStr</span><span class="o">::</span><span class="n">from_ptr</span><span class="p">(</span><span class="n">filename_ptr</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">let</span> <span class="n">target_movie</span> <span class="o">=</span> <span class="n">unsafe</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="no">CStr</span><span class="o">::</span><span class="n">from_ptr</span><span class="p">(</span><span class="n">target_movie_ptr</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">let</span> <span class="n">result</span> <span class="o">=</span> <span class="n">find_actors</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">str</span><span class="o">::</span><span class="n">from_utf8</span><span class="p">(</span><span class="n">filename</span><span class="o">.</span><span class="n">to_bytes</span><span class="p">())</span><span class="o">.</span><span class="n">unwrap</span><span class="p">()</span><span class="o">.</span><span class="n">to_string</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">skip_lines</span> <span class="n">as</span> <span class="n">usize</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">str</span><span class="o">::</span><span class="n">from_utf8</span><span class="p">(</span><span class="n">target_movie</span><span class="o">.</span><span class="n">to_bytes</span><span class="p">())</span><span class="o">.</span><span class="n">unwrap</span><span class="p">()</span><span class="o">.</span><span class="n">to_string</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="no">CString</span><span class="o">::</span><span class="kp">new</span><span class="p">(</span><span class="n">result</span><span class="p">)</span><span class="o">.</span><span class="n">unwrap</span><span class="p">()</span><span class="o">.</span><span class="n">as_ptr</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="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>Ou seja, eu fiz uma nova função que consome a que analisamos antes. A que fizemos primeiro recebe structs String e devolve uma String, que são structs de Rust. Não entendi ainda como converter isso automaticamente para ser consumido externamente. Então essa função acima recebe de fora do Rust um ponteiro para uma lista de chars (que é o conceito original de uma &ldquo;string&rdquo;/&ldquo;corrente&rdquo;), pega o ponteiro, pega os bytes do local onde o ponteiro aponta, e cria uma &lsquo;str&rsquo; UTF-8, e finalmente chama &rsquo;to_string()&rsquo; pra gerar uma String de Rust.</p>
<p>Então ele pega o resultado, que é uma String, cria um CString (que vem do módulo &ldquo;std::ffi&rdquo;), ou seja, uma &ldquo;String de C&rdquo; e devolve o ponteiro para fora. Note que nos casos onde recebemos ponteiros, declaramos que é um bloco <a href="http://doc.rust-lang.org/book/unsafe.html"target="_blank" rel="noopener">&ldquo;unsafe&rdquo;</a>.</p>
<p>A diretiva <tt>#[no_mangle]</tt> é para o compilador do Rust manter e não bagunçar o nome da função internamente. E <tt>pub extern</tt> é para declará-la disponível para ser usado publicamente e externamente.</p>
<p>Sinceramente, não sei se essa é a forma correta de se expôr uma função. Provalvemente de um jeito melhor e mais simples, mas ainda não encontrei. Se alguém souber como, não deixe de colocar nos comentários.</p>
<p>Então, do lado do Ruby, consumimos desta forma:</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;ffi&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">RustWorld</span>
</span></span><span class="line"><span class="cl">  <span class="kp">extend</span> <span class="no">FFI</span><span class="o">::</span><span class="no">Library</span>
</span></span><span class="line"><span class="cl">  <span class="n">ffi_lib</span> <span class="s1">&#39;target/release/libimdb.so&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">attach_function</span> <span class="ss">:ffi_find_actors</span><span class="p">,</span> <span class="o">[</span><span class="ss">:string</span><span class="p">,</span> <span class="ss">:int</span><span class="p">,</span> <span class="ss">:string</span><span class="o">]</span><span class="p">,</span> <span class="ss">:string</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>Note que estamos fazendo link com a versão de &ldquo;release&rdquo; gerado via <tt>cargo build &ndash;release</tt>. E então declaramos a assinatura da função que queremos usar.</p>
<p>Finalmente, podemos usar dentro do Ruby normalmente 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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">RustWorld</span><span class="o">.</span><span class="n">ffi_find_actors</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="mi">239</span><span class="p">,</span> <span class="n">target_movie</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>
<h2>Comparação de Performance<span class="hx:absolute hx:-mt-20" id="comparação-de-performance"></span>
    <a href="#compara%c3%a7%c3%a3o-de-performance" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui vem uma pequena surpresa. Eu fiz esse código com uma versão em Ruby e outra em Rust, lendo e processando o mesmo arquivo, para obter o mesmo resultado final. O que tive foi o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>RUST=1 ruby actors.rb                                                                                                                         0.070000   0.010000   0.080000 (  0.079534)

ruby actors.rb                                                                                                                                0.060000   0.000000   0.060000 (  0.057541)</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>Ou seja, a versão Ruby é um pouco mais rápido que a versão em Rust, por uma margem de 27% (!!). Esses tempos foram marcados internamente dentro do Ruby (consumindo o Rust via FFI) com a biblioteca Benchmark.</p>
<p>E medindo diretamente, calculando o tempo com a função &ldquo;time&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>time cargo run --release                                                                                                                    cargo run --release  0.11s user 0.04s system 99% cpu 0.156 total

time ruby actors.rb                                                                                                                         ruby actors.rb  0.28s user 0.03s system 98% cpu 0.311 total</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>Aqui vemos o Ruby sendo mais lento. Como o arquivo de testes é muito pequeno, o tempo de subir o Ruby interfere na medição. Então vamos tentar com outro arquivo maior, com 52MB em vez de meros 515k:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>time cargo run --release
cargo run --release  7.04s user 0.11s system 99% cpu 7.184 total

time ruby actors.rb
ruby actors.rb  6.69s user 0.08s system 99% cpu 6.808 total</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>Ou seja, o Ruby ainda é mais rápido que a versão Rust. E aqui podemos ficar confusos: o Rust, sendo muito mais próximo de C do que de Ruby, não deveria ser algumas ordens de grandeza mais rápido?</p>
<p>O código em si é muito simples. O primeiro ponto é que ele depende mais de I/O (ler o arquivo). E nisso o Ruby é muito rápido pois essa lógica é implementada internamente em C.</p>
<p>A segunda parte que eu imagino mais pesada é processar a Regex linha a linha. E nesse caso a engine de Regex do Ruby é bastante rápida, também internamente sendo feita em C - mais do que isso, ela é madura, tendo passado por inúmeras reescritas e refatoramentos nos últimos anos. E a biblioteca Regex do Rust eu imagino que, por ser ainda imatura, tem muito a ser otimizada e isso está segurando os números.</p>
<p>Outra coisa: é um processamento sequencial, linear, do arquivo. Se gastássemos mais tempo em particionar o arquivo e rodar pedaços em paralelo para maximizar o uso da máquina, imagino que o Rust talvez tivesse alguma vantagem, mas mesmo nesse caso usaríamos alguma coisa como a biblioteca <a href="https://github.com/grosser/parallel"target="_blank" rel="noopener">Grosser/Parallel</a> do Ruby, como <a href="http://www.akitaonrails.com/2015/05/15/small-bites-brincando-de-crawlers-e-algumas-dicas-uteis#.VXHlGZ9Hlp8"target="_blank" rel="noopener">já expliquei em outro artigo</a>, pra conseguir também com Ruby usar todas as CPUs da máquina.</p>
<p>Na verdade, o problema não é o Rust ser &ldquo;lento&rdquo; mas subestimarmos o Ruby achando que ele sempre vai ser lento em tudo quando na verdade não é o caso. Faça a lição de casa antes de assumir que algo é lento ou rápido, você vai se surpreender em ver como Ruby é muito - e rápido - em diversos tipos de tarefas que à primeira vista parece que não. E não considerem que &ldquo;Rust&rdquo; é ruim por causa deste teste: é um caso de uso específico, que depende mais da forma como a biblioteca de Regex (possivelmente) amadureceu até este ponto.</p>
<p><strong>Obs 05/06/15:</strong> Logo após publicar o post o camarada Jeffry DeGrande mandou um <a href="https://github.com/akitaonrails/rust_ruby_exercise_1/pull/1"target="_blank" rel="noopener">Pull Request</a> que troca a lenta crate &ldquo;regex&rdquo; pela &ldquo;pcre&rdquo; que, como seu nome diz, linka por baixo com a boa e velha biblioteca nativa &ldquo;libpcre3&rdquo; (Perl Compatible Regular Expression). Com isso os tempos ficam <strong>Muito</strong> melhores:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&gt; RUST=1 ruby actors.rb
running Rust/FFI version
  1.600000   0.010000   1.610000 (  1.625171)

&gt; ruby actors.rb
running pure Ruby version
  5.980000   0.050000   6.030000 (  6.046123)</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>Como eu suspeitava, a culpada era mesmo a biblioteca imatura de regex. Trocando pela pcre a implementação em Rust fica na menos que <strong>3.7</strong> vezes mais rápida que a em Ruby, que seria um tempo que deveríamos mesmo esperar de uma linguagem compilada!</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Rust atingiu sua versão 1.0 em Maio de 2015, ou seja, poucos dias atrás. Significa que muita coisa que você encontrar na Web, blogs, foruns, estarão defasados. Eu tive muita dificuldade em encontrar bons exemplos de código que funcionam na versão 1.0. Algumas structs mudaram de módulo. Alguns métodos estão com assinaturas diferentes e retornando coisas diferentes. A própria documentação oficial está com alguns erros ainda. Então tenha um pouco de paciência pois é a partir de agora com bons materiais vão começar a surgir.</p>
<p>Quem investiu tempo em aprender C/C++ (ou pelo menos Objective-C pré-ARC) não vai estranhar tanto assim o modelo de <a href="http://pcwalton.github.io/blog/2013/03/18/an-overview-of-memory-management-in-rust/"target="_blank" rel="noopener">gerenciamento de memória</a> do Rust. A idéia de ownership/borrowing não é tão diferente assim de um retain/release. Só que em vez de contar referências, você só pode &ldquo;emprestar&rdquo; uma vez. Além disso os valores são imutáveis, então é um modelo mais simples - embora mais difícil de se adaptar logo de cara. Especialmente importante é entender o modelo de <a href="http://words.steveklabnik.com/a-new-introduction-to-rust"target="_blank" rel="noopener">alocar no Stack</a> em vez de na Heap.</p>
<p>Também não há códigos de erro de retorno nem Exceptions. Aprender a lidar com o modelo de Pattern Matching em estruturas de Result/Option é outra mudança na forma de programar. Minha recomendação é abrir códigos fontes de terceiros como o próprio Cargo, para ver como são usados na prática.</p>
<p>Uma das coisas que não toquei neste artigo é seu excelente suporte a concorrência e paralelismo. Leia o exemplo do <a href="http://doc.rust-lang.org/book/dining-philosophers.html"target="_blank" rel="noopener">Dining Philosopher</a> e como o Rust resolve esse clássico problema. É bem simples mesmo entender.</p>
<p>Para quem acha que o mundo é orientado a objetos, Rust é mais uma linguagem que - embora tenha alguma semântica de objetos - não é orientado a objetos. Ele tem structs e funções associadas a structs. Entender o modelo de <a href="http://blog.rust-lang.org/2015/05/11/traits.html"target="_blank" rel="noopener">Traits</a> é crucial.</p>
<p>Rust é uma linguagem compilada, então toda vez que fizer algum código, existe o ciclo de compilar antes de executar, coisa que esquecemos como é no mundo Ruby ou Javascript. Mas a existência do Cargo facilita completamente esse fluxo e torna a experiência mil vezes mais agradável do que no mundo C, onde iríamos ter que mexer com Makefiles ou coisa similar. A organização padrão de projetos gerados pelo Cargo, empacotamentos em Crates e gerenciamento de dependências largamente inspirado no Bundler (que, aliás, todo mundo copia hoje em dia pois é o melhor modelo), ajuda muito ainda mais no começo.</p>
<p>Acompanhem o repositório no Github chamado <a href="https://github.com/kud1ing/awesome-rust"target="_blank" rel="noopener">Awesome Rust</a> que tem diversos links de projetos open source. Outro repositório é o <a href="https://github.com/ctjhoa/rust-learning"target="_blank" rel="noopener">Rust Learning</a> com links para várias documentações importantes para aprender. Existe já o rascunho de um <a href="http://aturon.github.io/README.html"target="_blank" rel="noopener">&ldquo;guideline de estilo&rdquo;</a>.</p>
<p>Finalmente, Rust não é uma linguagem simples, definitivamente nem um pouco perto de algo como Ruby ou mesmo Elixir. Ele é mais baixo nível e vejo aplicações para coisas em nível de sistema. Ferramentas de linha de comando pra Linux. Bibliotecas para processamento de imagens, processamento numérico. Coisas que possam ser consumidas por outras linguagens ou aplicações, como o exemplo de expor funcionalidades como extensions nativas para Rubygems. Nesse sentido ele é mais seguro em termos de gerenciamento de memória, e com performance de processamento comparável a C++. Então é uma boa chance para programadores de linguagens de alto nível como Ruby ou Swift conseguirem descer para o nível de sistema sem precisar perder a cabeça com a complexidade de C/C++.</p>
]]></content:encoded><category>rust</category><category>ruby</category><category>performance</category></item></channel></rss>