<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Crystal | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/crystal/</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:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/crystal/index.xml" rel="self" type="application/rss+xml"/><item><title>Portando 10 Mil Linhas de Python pra Crystal com Claude: easy-subtitle</title><link>https://akitaonrails.github.io/2026/03/07/portando-10-mil-linhas-de-python-pra-crystal-com-claude-easy-subtitle/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/03/07/portando-10-mil-linhas-de-python-pra-crystal-com-claude-easy-subtitle/</guid><pubDate>Sat, 07 Mar 2026 22:00:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="https://akitaonrails.github.io/2026/03/07/crystal-e-um-wrapper-inteligente-pro-ffmpeg-feito-em-3-horas-easy-ffmpeg/"&gt;artigo anterior&lt;/a&gt; eu mostrei por que escolhi Crystal pra CLIs de linha de comando. Nesse artigo eu quero mostrar um caso mais ambicioso. Não é mais uma ferramenta do zero &amp;ndash; é um port feature-parity de um projeto open source de 10 mil linhas de Python.&lt;/p&gt;
&lt;h2&gt;O Problema: Legendas&lt;span class="hx:absolute hx:-mt-20" id="o-problema-legendas"&gt;&lt;/span&gt;
&lt;a href="#o-problema-legendas" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Quem mantém uma coleção de filmes e séries sabe a dor. Você baixa o arquivo MKV, mas a legenda embutida está dessincronizada. Ou pior: não tem legenda nenhuma no idioma que você quer. Aí você vai no OpenSubtitles, baixa uma legenda, e ela tá 3 segundos adiantada porque foi feita pra outro release. O fluxo manual é:&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="/2026/03/07/crystal-e-um-wrapper-inteligente-pro-ffmpeg-feito-em-3-horas-easy-ffmpeg/">artigo anterior</a> eu mostrei por que escolhi Crystal pra CLIs de linha de comando. Nesse artigo eu quero mostrar um caso mais ambicioso. Não é mais uma ferramenta do zero &ndash; é um port feature-parity de um projeto open source de 10 mil linhas de Python.</p>
<h2>O Problema: Legendas<span class="hx:absolute hx:-mt-20" id="o-problema-legendas"></span>
    <a href="#o-problema-legendas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quem mantém uma coleção de filmes e séries sabe a dor. Você baixa o arquivo MKV, mas a legenda embutida está dessincronizada. Ou pior: não tem legenda nenhuma no idioma que você quer. Aí você vai no OpenSubtitles, baixa uma legenda, e ela tá 3 segundos adiantada porque foi feita pra outro release. O fluxo manual é:</p>
<ol>
<li>Extrair tracks de legenda do MKV com <code>mkvextract</code></li>
<li>Ir no OpenSubtitles, procurar legenda pro filme</li>
<li>Baixar, testar, ver que tá fora de sync</li>
<li>Rodar algum tool de sync (ffsubsync, alass)</li>
<li>Renomear o arquivo, mover pro lugar certo</li>
<li>Repetir pra cada idioma, pra cada filme</li>
</ol>
<p>Pra quem tem 10 filmes, é tolerável. Pra quem tem centenas, é insano. É exatamente o tipo de coisa que deveria ser automatizada.</p>
<h2>Subservient: A Solução Python<span class="hx:absolute hx:-mt-20" id="subservient-a-solução-python"></span>
    <a href="#subservient-a-solu%c3%a7%c3%a3o-python" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pesquisando o que já existia, encontrei o <a href="https://github.com/N3xigen/Subservient"target="_blank" rel="noopener">Subservient</a>. É um projeto Python que automatiza exatamente esse fluxo: extrai legendas de MKVs, baixa do OpenSubtitles via API REST, sincroniza com ffsubsync, e limpa propagandas dos arquivos SRT.</p>
<p>O projeto é completo. Tem modo filme e modo série, smart sync (testa todos os candidatos em paralelo e pega o melhor) e first-match (para no primeiro que funcionar). Usa o hash do OpenSubtitles pra matching exato e limpa watermarks e propagandas com mais de 30 regex patterns.</p>
<p>Mas tem os problemas típicos de distribuição Python:</p>
<ul>
<li><strong>7 dependências pip</strong>: colorama, requests, langdetect, ffsubsync, platformdirs, pycountry, tqdm</li>
<li><strong>ffsubsync como engine de sync</strong>: que por sua vez depende de numpy, auditok, e mais um monte de pacote Python</li>
<li><strong>Interface interativa com menus</strong>: boa pra uso manual, péssima pra scriptabilidade</li>
<li><strong>Config em formato INI</strong>: não é o fim do mundo, mas YAML é mais ergonômico</li>
<li><strong>10.220 linhas em 6 arquivos Python</strong>: arquivos de 2.700 linhas com funções de centenas de linhas cada</li>
</ul>
<p>O ponto não é que Python seja ruim pra isso. O Subservient funciona. Mas instalar e manter em produção é outra história. Se você quer rodar num server headless, precisa de Python 3.8+, pip, virtualenv (ou vai poluir o sistema), e torcer pra nenhuma dependência quebrar com a próxima atualização do OS.</p>
<h2>O Experimento: Port pra Crystal com Claude<span class="hx:absolute hx:-mt-20" id="o-experimento-port-pra-crystal-com-claude"></span>
    <a href="#o-experimento-port-pra-crystal-com-claude" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui é onde a coisa fica interessante. Eu queria testar uma hipótese: o Claude consegue pegar um projeto open source grande, entender a arquitetura, e fazer um port completo pra outra linguagem?</p>
<p>Não estou falando de traduzir arquivo por arquivo. Estou falando de entender o que o projeto faz, redesenhar a arquitetura onde faz sentido, e gerar código idiomático em Crystal.</p>
<p>O que eu fiz:</p>
<ol>
<li>Pedi pro Claude clonar e analisar o repositório do Subservient</li>
<li>Expliquei as decisões de design: usar <a href="https://github.com/kaegi/alass"target="_blank" rel="noopener">alass</a> (binário Rust, sem dependências Python) no lugar do ffsubsync, subcomandos CLI ao invés de menus interativos, YAML ao invés de INI</li>
<li>Pedi port feature-parity, com testes</li>
</ol>
<p>O alass é um detalhe importante. O ffsubsync funciona bem, mas é um pacote Python que puxa numpy e faz análise de áudio. O alass faz a mesma coisa (sincronização de legendas por análise de timing), mas é um binário Rust standalone. Trocar um pelo outro elimina a maior dependência Python do stack.</p>
<h2>O Resultado: easy-subtitle<span class="hx:absolute hx:-mt-20" id="o-resultado-easy-subtitle"></span>
    <a href="#o-resultado-easy-subtitle" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Cinco commits. Menos de 40 minutos do primeiro ao último.</p>
<table>
  <thead>
      <tr>
          <th>Commit</th>
          <th>Hora</th>
          <th>O que fez</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Initial implementation</td>
          <td>21:47</td>
          <td>Port completo: 42 arquivos src, 16 arquivos de teste, CI, install script</td>
      </tr>
      <tr>
          <td>Track shard.lock</td>
          <td>21:56</td>
          <td>Lock de dependências pra builds reproduzíveis</td>
      </tr>
      <tr>
          <td>Prefer ~/.local/bin</td>
          <td>22:03</td>
          <td>Fix no install script</td>
      </tr>
      <tr>
          <td>Add doctor command</td>
          <td>22:20</td>
          <td>Novo comando <code>doctor</code> pra validar setup + bump v0.2.0</td>
      </tr>
      <tr>
          <td>Homebrew formula</td>
          <td>22:24</td>
          <td>Suporte a <code>brew install</code> e workflow de auto-update</td>
      </tr>
  </tbody>
</table>
<p>O primeiro commit já entrega o projeto funcional: 8 comandos CLI, cliente OpenSubtitles com rate limiting, 76 testes passando, GitHub Actions com CI e release pra Linux e macOS.</p>
<h3>Números<span class="hx:absolute hx:-mt-20" id="números"></span>
    <a href="#n%c3%bameros" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Métrica</th>
          <th>Subservient (Python)</th>
          <th>easy-subtitle (Crystal)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Código fonte</td>
          <td>10.220 linhas (6 arquivos)</td>
          <td>2.516 linhas (42 arquivos)</td>
      </tr>
      <tr>
          <td>Testes</td>
          <td>0</td>
          <td>800 linhas (76 specs)</td>
      </tr>
      <tr>
          <td>Dependências runtime</td>
          <td>7 pacotes pip + ffsubsync</td>
          <td>0 (só webmock pra testes)</td>
      </tr>
      <tr>
          <td>Binário</td>
          <td>n/a (precisa Python + deps)</td>
          <td>~6MB estático</td>
      </tr>
      <tr>
          <td>Config</td>
          <td>INI</td>
          <td>YAML</td>
      </tr>
      <tr>
          <td>Sync engine</td>
          <td>ffsubsync (Python)</td>
          <td>alass (Rust)</td>
      </tr>
      <tr>
          <td>UI</td>
          <td>Menu interativo</td>
          <td>Subcomandos CLI</td>
      </tr>
      <tr>
          <td>Concorrência</td>
          <td>ThreadPoolExecutor</td>
          <td>Crystal fibers + channels</td>
      </tr>
  </tbody>
</table>
<p>A diferença de LOC é gritante: 10.220 vs 2.516. Mas isso não é mérito só do Crystal. O Python original tem arquivos monolíticos de milhares de linhas, com muita duplicação e lógica de UI misturada com lógica de negócio. O port separa as responsabilidades em módulos pequenos e focados.</p>
<h3>Arquitetura do Port<span class="hx:absolute hx:-mt-20" id="arquitetura-do-port"></span>
    <a href="#arquitetura-do-port" 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><pre><code>easy-subtitle/
  src/easy_subtitle/
    cli/           # Router &#43; 9 comandos (init, extract, download, sync, run, clean, scan, hash, doctor)
    core/          # Mapa de idiomas, parser/writer/cleaner de SRT, scanner de vídeo
    acquisition/   # Cliente API OpenSubtitles, auth, busca, download, movie hash
    extraction/    # Parsing de tracks MKV, extração, remuxing
    synchronization/  # Runner do alass, cálculo de offset, estratégias smart/first-match
    models/        # VideoFile, SubtitleCandidate, CoverageEntry</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>Cada módulo tem uma responsabilidade clara. O maior arquivo tem 144 linhas (config). No Python original, o <code>acquisition.py</code> sozinho tem 2.726 linhas.</p>
<h3>O Que Cada Comando Faz<span class="hx:absolute hx:-mt-20" id="o-que-cada-comando-faz"></span>
    <a href="#o-que-cada-comando-faz" 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"><span class="c1"># Gerar config padrão</span>
</span></span><span class="line"><span class="cl">easy-subtitle init
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Extrair legendas de dentro de MKVs</span>
</span></span><span class="line"><span class="cl">easy-subtitle extract /path/to/movies
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Baixar legendas do OpenSubtitles</span>
</span></span><span class="line"><span class="cl">easy-subtitle download -l en,pt /path/to/movies
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Sincronizar legendas baixadas com o vídeo</span>
</span></span><span class="line"><span class="cl">easy-subtitle sync /path/to/movies
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Pipeline completo: extract → download → sync</span>
</span></span><span class="line"><span class="cl">easy-subtitle run /path/to/movies
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Limpar propagandas/watermarks de SRTs</span>
</span></span><span class="line"><span class="cl">easy-subtitle clean /path/to/subtitles
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Ver cobertura de legendas por idioma</span>
</span></span><span class="line"><span class="cl">easy-subtitle scan --json /path/to/movies
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Computar hash OpenSubtitles (debug)</span>
</span></span><span class="line"><span class="cl">easy-subtitle <span class="nb">hash</span> /path/to/movie.mkv
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Validar setup: config, credenciais, dependências</span>
</span></span><span class="line"><span class="cl">easy-subtitle doctor</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>doctor</code> é um comando que eu adicionei depois. Ele verifica se o config existe, se a API key está configurada, testa login na API, e checa se <code>mkvmerge</code>, <code>mkvextract</code> e <code>alass</code> estão no PATH. Mostra instruções de instalação específicas pro OS quando algo falta.</p>
<h3>Smart Sync com Fibers<span class="hx:absolute hx:-mt-20" id="smart-sync-com-fibers"></span>
    <a href="#smart-sync-com-fibers" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O smart sync é a parte que eu mais gostei de ver o port. No Python original, ele usa <code>ThreadPoolExecutor</code> pra rodar múltiplos candidatos em paralelo. Em Crystal, a mesma lógica fica mais natural com fibers e channels:</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-crystal" data-lang="crystal"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="n">candidates</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">Path</span><span class="p">),</span> <span class="n">video</span> <span class="p">:</span> <span class="n">VideoFile</span><span class="p">)</span> <span class="p">:</span> <span class="n">SyncResult?</span>
</span></span><span class="line"><span class="cl">  <span class="n">channel</span> <span class="o">=</span> <span class="nb">Channel</span><span class="p">(</span><span class="n">SyncResult</span><span class="p">)</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">candidates</span><span class="o">.</span><span class="n">size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">candidates</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">candidate</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="bp">spawn</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">result</span> <span class="o">=</span> <span class="n">sync_one</span><span class="p">(</span><span class="n">candidate</span><span class="p">,</span> <span class="n">video</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">channel</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">result</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></span><span class="line"><span class="cl">  <span class="n">results</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">(</span><span class="n">SyncResult</span><span class="p">)</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">candidates</span><span class="o">.</span><span class="n">size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">candidates</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">times</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">results</span> <span class="o">&lt;&lt;</span> <span class="n">channel</span><span class="o">.</span><span class="n">receive</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">accepted</span> <span class="o">=</span> <span class="n">results</span><span class="o">.</span><span class="n">select</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">accepted?</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">accepted</span><span class="o">.</span><span class="n">min_by</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">offset</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>Cada candidato de legenda é sincronizado em um fiber separado (via <code>spawn</code>). Os resultados voltam pelo <code>Channel</code>. No final, pega o aceito com menor offset. Sem ThreadPoolExecutor, sem futures, sem callbacks.</p>
<h3>Rate Limiting da API<span class="hx:absolute hx:-mt-20" id="rate-limiting-da-api"></span>
    <a href="#rate-limiting-da-api" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O OpenSubtitles exige throttling de 500ms entre requests. O cliente Crystal implementa isso com Mutex:</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-crystal" data-lang="crystal"><span class="line"><span class="cl"><span class="no">RATE_LIMIT_MS</span> <span class="o">=</span> <span class="mi">500</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">private</span> <span class="k">def</span> <span class="nf">throttle!</span> <span class="p">:</span> <span class="nb">Nil</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@mutex</span><span class="o">.</span><span class="n">synchronize</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">elapsed</span> <span class="o">=</span> <span class="nb">Time</span><span class="o">.</span><span class="n">utc</span> <span class="o">-</span> <span class="vi">@last_request_at</span>
</span></span><span class="line"><span class="cl">    <span class="n">remaining</span> <span class="o">=</span> <span class="no">RATE_LIMIT_MS</span> <span class="o">-</span> <span class="n">elapsed</span><span class="o">.</span><span class="n">total_milliseconds</span>
</span></span><span class="line"><span class="cl">    <span class="nb">sleep</span><span class="p">(</span><span class="n">remaining</span><span class="o">.</span><span class="n">milliseconds</span><span class="p">)</span> <span class="k">if</span> <span class="n">remaining</span> <span class="o">&gt;</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@last_request_at</span> <span class="o">=</span> <span class="nb">Time</span><span class="o">.</span><span class="n">utc</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></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>Simples, thread-safe, sem biblioteca externa.</p>
<h3>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></h3><p>O binário estático sai do GitHub Actions e pode ser instalado de três 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 / Linux)</span>
</span></span><span class="line"><span class="cl">brew install akitaonrails/tap/easy-subtitle
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Script de instalação</span>
</span></span><span class="line"><span class="cl">curl -fsSL https://raw.githubusercontent.com/akitaonrails/easy-subtitle/master/install.sh <span class="p">|</span> bash
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Ou baixa o binário direto dos Releases</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>Um binário. Sem Python, sem pip, sem nada.</p>
<h2>Sobre Portar Coisas &ldquo;Só Porque Sim&rdquo;<span class="hx:absolute hx:-mt-20" id="sobre-portar-coisas-só-porque-sim"></span>
    <a href="#sobre-portar-coisas-s%c3%b3-porque-sim" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu sempre defendi que portar software de uma linguagem pra outra só pelo fetiche da linguagem é perda de tempo. Quantos projetos foram reescritos em Rust &ldquo;porque sim&rdquo;? Quanto esforço gasto em reescritas que não entregaram valor novo?</p>
<p>Mas preciso admitir que esse experimento me fez reconsiderar.</p>
<p>Quando o custo de portar cai de semanas/meses pra menos de 40 minutos, a equação muda. O port do Subservient pra Crystal com Claude não foi um exercício de vaidade linguística. Eu queria um binário estático que eu pudesse jogar num server e esquecer. Sem gerenciar runtime Python, sem pip install quebrando na próxima atualização do sistema.</p>
<p>E o resultado não é um port mecânico. São 2.516 linhas em 42 arquivos, contra 10.220 em 6 monolíticos. O port veio com 76 testes que o original não tinha, CI com release automático pra Linux e macOS, Homebrew formula e install script com verificação de checksum.</p>
<p>O ponto não é que Python é ruim. É que a barreira de &ldquo;será que vale a pena portar?&rdquo; ficou ridiculamente baixa. Port feature-parity com testes em menos de uma hora. Difícil argumentar contra.</p>
<p>O <a href="https://github.com/akitaonrails/easy-subtitle"target="_blank" rel="noopener">repositório está aqui</a>. GPL-3.0, como o original.</p>
]]></content:encoded><category>crystal</category><category>linguagens-de-programacao</category><category>vibe-coding</category></item><item><title>Crystal e um wrapper inteligente pro FFmpeg feito em 3 horas | easy-ffmpeg</title><link>https://akitaonrails.github.io/2026/03/07/crystal-e-um-wrapper-inteligente-pro-ffmpeg-feito-em-3-horas-easy-ffmpeg/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/03/07/crystal-e-um-wrapper-inteligente-pro-ffmpeg-feito-em-3-horas-easy-ffmpeg/</guid><pubDate>Sat, 07 Mar 2026 18:00:00 -0300</pubDate><description>&lt;p&gt;Todo mundo que já precisou converter um vídeo no terminal conhece a dor do FFmpeg. Ele faz tudo. Absolutamente tudo. Mas pra fazer qualquer coisa, você precisa lembrar combinações de flags que parecem feitiço:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ffmpeg -i input.mkv -c:v libx264 -crf &lt;span class="m"&gt;23&lt;/span&gt; -preset medium &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -profile:v high -level 4.1 -c:a aac -b:a 128k &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -movflags +faststart output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;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"&gt;
&lt;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"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;O que isso faz? Converte pra H.264 com qualidade razoável pra web, usando AAC no áudio e movendo o moov atom pro início do arquivo pra permitir streaming progressivo. Se você já sabia disso, parabéns. Se não sabia, bem-vindo ao clube de 99% das pessoas que usam FFmpeg copiando comandos do Stack Overflow.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Todo mundo que já precisou converter um vídeo no terminal conhece a dor do FFmpeg. Ele faz tudo. Absolutamente tudo. Mas pra fazer qualquer coisa, você precisa lembrar combinações de flags que parecem feitiço:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ffmpeg -i input.mkv -c:v libx264 -crf <span class="m">23</span> -preset medium <span class="se">\
</span></span></span><span class="line"><span class="cl">  -profile:v high -level 4.1 -c:a aac -b:a 128k <span class="se">\
</span></span></span><span class="line"><span class="cl">  -movflags +faststart output.mp4</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 que isso faz? Converte pra H.264 com qualidade razoável pra web, usando AAC no áudio e movendo o moov atom pro início do arquivo pra permitir streaming progressivo. Se você já sabia disso, parabéns. Se não sabia, bem-vindo ao clube de 99% das pessoas que usam FFmpeg copiando comandos do Stack Overflow.</p>
<p>E esse é o caso simples. Quer fazer um GIF animado a partir de uma sequência de PNGs? Precisa de um pipeline de duas passadas com geração de paleta:</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">ffmpeg -framerate <span class="m">10</span> -i frame_%04d.png <span class="se">\
</span></span></span><span class="line"><span class="cl">  -vf <span class="s2">&#34;split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse&#34;</span> output.gif</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 cortar um trecho, redimensionar pra 720p e forçar aspect ratio 16:9? Boa sorte montando o filter chain.</p>
<p>Eu cansei de ficar decorando flags. Queria uma CLI que eu falasse &ldquo;converte isso pra MP4 otimizado pra web&rdquo; e ela resolvesse. Então fiz o <a href="https://github.com/akitaonrails/easy-ffmpeg"target="_blank" rel="noopener">easy-ffmpeg</a>.</p>
<h2>O que o easy-ffmpeg faz<span class="hx:absolute hx:-mt-20" id="o-que-o-easy-ffmpeg-faz"></span>
    <a href="#o-que-o-easy-ffmpeg-faz" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>É um wrapper inteligente. Você dá o arquivo de entrada e o formato de saída, e ele analisa o vídeo com ffprobe, descobre quais streams de vídeo, áudio e legenda existem, verifica compatibilidade de codecs com o container de destino, e decide sozinho o que pode copiar direto (sem re-encoding, instantâneo) e o que precisa transcodificar.</p>
<p>O uso mais básico:</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"># Converter MKV pra MP4 (copia streams compatíveis, sem re-encoding desnecessário)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Otimizado pra web (H.264 + AAC, faststart)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4 --web
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Otimizado pra celular (720p, AAC stereo, arquivo menor)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4 --mobile
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Compressão máxima (H.265, CRF 28)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4 --compress
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Qualidade alta pra streaming (H.265, CRF 18)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg input.mkv mp4 --streaming</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>Exemplos do que dá pra fazer<span class="hx:absolute hx:-mt-20" id="exemplos-do-que-dá-pra-fazer"></span>
    <a href="#exemplos-do-que-d%c3%a1-pra-fazer" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><strong>Cortar um trecho do vídeo:</strong></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"># Do minuto 1:30 até 3:00</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --start 1:30 --end 3:00
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Os primeiros 90 segundos</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --duration <span class="m">90</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Aceita vários formatos de tempo: 90, 1:30, 01:30.5, 1:02:30</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><strong>Redimensionar:</strong></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"># Pra 720p</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --scale hd
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Pra 1080p</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --scale fullhd
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Presets: 2k (1440p), fullhd (1080p), hd (720p), retro (480p), icon (240p)</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><strong>Mudar aspect ratio:</strong></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"># Forçar 16:9 (adiciona barras pretas se necessário)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --aspect wide
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Formato TikTok/Stories (9:16 vertical)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --aspect tiktok
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Quadrado pro Instagram</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --aspect square
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Croppar em vez de adicionar barras</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mp4 mp4 --aspect wide --crop</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><strong>Sequência de imagens pra vídeo:</strong></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"># Detecta automaticamente o padrão de numeração (frame_0001.png, frame_0002.png...)</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg /pasta/com/frames/ mp4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># GIF animado com paleta otimizada</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg /pasta/com/frames/ gif
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Sequência de imagens em 720p a 30fps</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg /pasta/com/frames/ mp4 --fps <span class="m">30</span> --scale hd</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><strong>Ver o que vai fazer sem executar:</strong></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">easy-ffmpeg video.mkv mp4 --web --dry-run
</span></span><span class="line"><span class="cl"><span class="c1"># Mostra o comando ffmpeg exato que seria executado</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><strong>Combinar tudo:</strong></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"># Cortar, redimensionar e comprimir pra mandar por WhatsApp</span>
</span></span><span class="line"><span class="cl">easy-ffmpeg video.mkv mp4 --start 0:30 --end 2:00 --scale hd --compress</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 se você rodar <code>easy-ffmpeg</code> sem argumentos num terminal interativo, abre um modo TUI com seleção de arquivo por fuzzy search, escolha de preset por menu, e input de tempos com validação &ndash; tudo sem precisar lembrar de nenhuma flag.</p>
<h2>Por que Crystal<span class="hx:absolute hx:-mt-20" id="por-que-crystal"></span>
    <a href="#por-que-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Faz anos que eu não mexia com Crystal. A última vez foi pra brincar, antes da versão 1.0. E eu queria revisitar.</p>
<p>Crystal ocupa um nicho interessante. Go e Crystal competem pelo mesmo espaço: linguagens compiladas pra aplicações, que geram binários estáticos, com garbage collector e sem dependência de runtime. Mas a abordagem é bem diferente.</p>
<p>Go é famoso por ser deliberadamente simples. Sem generics por anos (só chegaram na 1.18), sem exceções (error returns), sem expressividade. O argumento é que isso facilita leitura e manutenção em equipes grandes. Na prática, resulta em código verboso e repetitivo, com <code>if err != nil</code> em todas as linhas.</p>
<p>Crystal tem tipagem estática com inferência, macros em tempo de compilação, blocos como closures (igual Ruby), exceções, generics desde sempre, e uma sintaxe que qualquer Rubista reconhece:</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-crystal" data-lang="crystal"><span class="line"><span class="cl"><span class="c1"># Crystal: ler um JSON e extrair dados</span>
</span></span><span class="line"><span class="cl"><span class="n">streams</span> <span class="o">=</span> <span class="n">json</span><span class="o">[</span><span class="s2">&#34;streams&#34;</span><span class="o">].</span><span class="n">as_a</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">StreamInfo</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="ss">codec</span><span class="p">:</span> <span class="n">s</span><span class="o">[</span><span class="s2">&#34;codec_name&#34;</span><span class="o">]?.</span><span class="n">try</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">as_s</span><span class="p">)</span> <span class="o">||</span> <span class="s2">&#34;unknown&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">width</span><span class="p">:</span>  <span class="n">s</span><span class="o">[</span><span class="s2">&#34;width&#34;</span><span class="o">]?.</span><span class="n">try</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">as_i</span><span class="p">)</span> <span class="o">||</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">height</span><span class="p">:</span> <span class="n">s</span><span class="o">[</span><span class="s2">&#34;height&#34;</span><span class="o">]?.</span><span class="n">try</span><span class="p">(</span><span class="o">&amp;.</span><span class="n">as_i</span><span class="p">)</span> <span class="o">||</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Go equivalente: seria 3x mais linhas com type assertions e error checks</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A stdlib do Crystal tem HTTP server, JSON parsing, YAML, regex, fibers (green threads com scheduler cooperativo), channels (igual Go), e até IO::FileDescriptor com raw mode pra terminal &ndash; que eu usei pro modo interativo. Concorrência funciona com <code>spawn</code> (equivalente ao <code>go</code> do Go) e <code>Channel</code> (idêntico ao chan do Go). A diferença é que tudo vem com a ergonomia do Ruby.</p>
<p>Pra uma CLI que precisa compilar num binário estático sem dependências, rodar em Linux e macOS, e ser distribuída como download direto &ndash; Crystal é perfeito. O compilador gera binários nativos, e com o Docker Alpine dá pra fazer static linking com musl pra Linux. O binário final do easy-ffmpeg tem ~6MB.</p>
<p>Eu considero Rust melhor pra código de sistemas (kernels, drivers, databases, coisas onde ownership e lifetime importam). Mas pra uma CLI de conversão de vídeo? Rust seria overkill. Crystal dá o mesmo resultado final (binário estático e rápido) com um terço do código e sem brigar com o borrow checker.</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><p>O projeto inteiro foi construído em uma tarde. 10 commits entre 18:32 e 21:33 do dia 7 de março de 2026. Três horas.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Linguagem    Arquivos    Linhas    Código
Crystal      13          2.823     2.342
Shell        1           109       91
Markdown     1           266       210
YAML         2           46        34
─────────────────────────────────────────
Total        20          3.326     2.419</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>2.342 linhas de Crystal fazem: CLI com 5 presets, análise de mídia via ffprobe, planejamento inteligente de conversão com matriz de compatibilidade de codecs, progress bar com ETA, modo interativo com fuzzy search, suporte a sequências de imagem e GIF, e trimming/scaling/aspect ratio com validação.</p>
<p>A ferramenta já está com CI/CD no GitHub Actions, compilando binários estáticos pra Linux (x86_64 e arm64) e macOS (arm64), com instalação via curl one-liner:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -fsSL https://raw.githubusercontent.com/akitaonrails/easy-ffmpeg/master/install.sh <span class="p">|</span> sh</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>Três horas de uma tarde, da primeira linha de código ao release no GitHub com binários pra três plataformas. O FFmpeg continua fazendo o trabalho pesado &ndash; eu só coloquei uma interface decente na frente.</p>
<p>O <a href="https://github.com/akitaonrails/easy-ffmpeg"target="_blank" rel="noopener">repositório está aqui</a>. MIT license.</p>
]]></content:encoded><category>crystal</category><category>audio-e-video</category><category>vibe-coding</category></item><item><title>Tentando igualar o Fast Blank em C usando Crystal</title><link>https://akitaonrails.github.io/2016/07/06/tentando-igualar-fast-blank-em-c-usando-crystal/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/07/06/tentando-igualar-fast-blank-em-c-usando-crystal/</guid><pubDate>Wed, 06 Jul 2016 17:10:00 -0300</pubDate><description>&lt;p&gt;Na minha visão, Crystal pode se tornar a solução ideal para deixar nossas queridas gems de Ruby mais rápidas. Até agora, temos usado extensões em C para acelerar código CPU-bound em Ruby. Nokogiri, por exemplo, é um wrapper que oferece uma API agradável em cima da libxml, que é uma biblioteca enorme escrita em C.&lt;/p&gt;
&lt;p&gt;Mas existem várias oportunidades para acelerar aplicações Rails também. Por exemplo, acabamos de ver o lançamento da &lt;a href="https://github.com/danielpclark/faster_path"target="_blank" rel="noopener"&gt;gem &amp;ldquo;faster_path&amp;rdquo;&lt;/a&gt;, dessa vez escrita em Rust e conectada via FFI (Foreign Function Interface). O autor afirma que o Sprockets precisa calcular muitos paths e fazer essa biblioteca, compilada nativamente e otimizada com Rust, trouxe uma melhora enorme na tarefa do asset pipeline.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Na minha visão, Crystal pode se tornar a solução ideal para deixar nossas queridas gems de Ruby mais rápidas. Até agora, temos usado extensões em C para acelerar código CPU-bound em Ruby. Nokogiri, por exemplo, é um wrapper que oferece uma API agradável em cima da libxml, que é uma biblioteca enorme escrita em C.</p>
<p>Mas existem várias oportunidades para acelerar aplicações Rails também. Por exemplo, acabamos de ver o lançamento da <a href="https://github.com/danielpclark/faster_path"target="_blank" rel="noopener">gem &ldquo;faster_path&rdquo;</a>, dessa vez escrita em Rust e conectada via FFI (Foreign Function Interface). O autor afirma que o Sprockets precisa calcular muitos paths e fazer essa biblioteca, compilada nativamente e otimizada com Rust, trouxe uma melhora enorme na tarefa do asset pipeline.</p>
<p>Sam Saffron, do Discourse, também construiu uma gem bem pequena chamada <a href="https://github.com/SamSaffron/fast_blank"target="_blank" rel="noopener">&ldquo;fast_blank&rdquo;</a>, uma minúscula biblioteca em C que reimplementa o método <code>String#blank?</code> do ActiveSupport para ser até 9x mais rápido. Como o Rails digere volumes de strings, checando se elas são &ldquo;blank&rdquo; o tempo todo, isso adiciona alguma performance (depende da sua aplicação, claro).</p>
<p>O Santo Graal da performance em nível nativo é conseguir escrever código próximo de Ruby ao invés de ter que mexer em C de baixo nível ou enfrentar a curva de aprendizado alta de uma linguagem como Rust. Mais que isso, eu gostaria de evitar usar FFI. Eu não sou um expert em FFI, mas lembro de ter entendido que ele adiciona overhead nas bindings.</p>
<p>Aliás, é importante deixar claro logo de cara: eu estou bem longe de ser um expert em C. Tenho pouquíssima experiência lidando com desenvolvimento hardcore em C. O que é justamente o motivo de essa possibilidade de escrever em Crystal ser tão atraente para mim. Então, se você é um expert em C e percebe alguma bobagem que estou falando, por favor me avise nos comentários abaixo.</p>
<p>Meu exercício é reescrever a gem Fast Blank, originalmente em C, em Crystal, adicioná-la na mesma gem para compilar usando Crystal se estiver disponível ou cair no fallback de C, e fazer os specs passarem para que seja uma transição transparente para o usuário.</p>
<p>Para conseguir isso eu tive que:</p>
<ul>
<li>Estender o <code>extconf.rb</code> da gem para gerar Makefiles diferentes (para C e Crystal) capazes de compilar em OS X ou Linux (Ubuntu pelo menos) - OK</li>
<li>Fazer os specs passarem na versão Crystal - Quase (está ok para a maior parte, exceto um edge case)</li>
<li>Fazer a performance ser mais rápida que Ruby e próxima de C - Ainda não tanto (no OS X a performance é bem boa, mas no Ubuntu não escala tão bem para strings grandes)</li>
</ul>
<p>Você pode conferir os resultados até agora no <a href="https://github.com/akitaonrails/fast_blank/tree/crystal_version"target="_blank" rel="noopener">meu fork no Github</a> e acompanhar a <a href="https://github.com/SamSaffron/fast_blank/pull/20"target="_blank" rel="noopener">discussão do Pull Request também</a>.</p>
<h3>Comparando C e Crystal<span class="hx:absolute hx:-mt-20" id="comparando-c-e-crystal"></span>
    <a href="#comparando-c-e-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Só para começar, vamos ver um trecho da versão original em C do Sam:</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-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">VALUE</span>
</span></span><span class="line"><span class="cl"><span class="nf">rb_str_blank</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">str</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">rb_encoding</span> <span class="o">*</span><span class="n">enc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="o">*</span><span class="n">s</span><span class="p">,</span> <span class="o">*</span><span class="n">e</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">enc</span> <span class="o">=</span> <span class="nf">STR_ENC_GET</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span> <span class="o">=</span> <span class="nf">RSTRING_PTR</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">s</span> <span class="o">||</span> <span class="nf">RSTRING_LEN</span><span class="p">(</span><span class="n">str</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">Qtrue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">e</span> <span class="o">=</span> <span class="nf">RSTRING_END</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">while</span> <span class="p">(</span><span class="n">s</span> <span class="o">&lt;</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">cc</span> <span class="o">=</span> <span class="nf">rb_enc_codepoint_len</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">n</span><span class="p">,</span> <span class="n">enc</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="p">(</span><span class="o">!</span><span class="nf">rb_isspace</span><span class="p">(</span><span class="n">cc</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">cc</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">Qfalse</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">s</span> <span class="o">+=</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="n">Qtrue</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>Sim, bem assustador, eu sei. Agora vamos ver a versão em Crystal:</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">struct</span> <span class="no">Char</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># mesma forma como o C Ruby implementa</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">is_blank</span>
</span></span><span class="line"><span class="cl">    <span class="nb">self</span> <span class="o">==</span> <span class="s1">&#39; &#39;</span> <span class="o">||</span> <span class="p">(</span><span class="s1">&#39;\t&#39;</span> <span class="o">&lt;=</span> <span class="nb">self</span> <span class="o">&lt;=</span> <span class="s1">&#39;\r&#39;</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></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">String</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">blank?</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span> <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span> <span class="nb">self</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">each_char</span> <span class="p">{</span> <span class="o">|</span><span class="n">char</span><span class="o">|</span> <span class="k">return</span> <span class="kp">false</span> <span class="k">if</span> <span class="o">!</span><span class="n">char</span><span class="o">.</span><span class="n">is_blank</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</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></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>Caraca! Se você é rubyista, aposto que entende 100% do snippet acima. Não é &ldquo;exatamente&rdquo; a mesma coisa (porque os specs ainda não estão passando completamente), mas chega bem perto.</p>
<h3>A busca por um Makefile para Crystal<span class="hx:absolute hx:-mt-20" id="a-busca-por-um-makefile-para-crystal"></span>
    <a href="#a-busca-por-um-makefile-para-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pesquisei vários <a href="https://github.com/akitaonrails/ruby_ext_in_crystal_math"target="_blank" rel="noopener">repositórios experimentais no Github</a> e Gists por aí. Mas não achei nenhum que tivesse tudo, então decidi adaptar o que encontrei até chegar a essa versão:</p>
<p>Obs: de novo, eu não sou expert em C. Se você tem experiência com Makefiles, sei que esse aqui dá para refatorar para algo mais bonito. Me avise nos comentários.</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-C" data-lang="C"><span class="line"><span class="cl"><span class="n">ifeq</span> <span class="s">&#34;$(PLATFORM)&#34;</span> <span class="s">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nl">PLATFORM</span> <span class="p">:</span><span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">uname</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">endif</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ifeq</span> <span class="s">&#34;$(PLATFORM)&#34;</span> <span class="s">&#34;Linux&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">UNAME</span> <span class="o">=</span> <span class="s">&#34;$(shell llvm-config --host-target)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">CRYSTAL_BIN</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">readlink</span> <span class="o">-</span><span class="n">f</span> <span class="err">`</span><span class="n">which</span> <span class="n">crystal</span><span class="err">`</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBRARY_PATH</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">dirname</span> <span class="err">$</span><span class="p">(</span><span class="n">CRYSTAL_BIN</span><span class="p">))</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">embedded</span><span class="o">/</span><span class="n">lib</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBCRYSTAL</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">dirname</span> <span class="err">$</span><span class="p">(</span><span class="n">CRYSTAL_BIN</span><span class="p">)</span> <span class="p">)</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">ext</span><span class="o">/</span><span class="n">libcrystal</span><span class="p">.</span><span class="n">a</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBRUBY</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">ruby</span> <span class="o">-</span><span class="n">e</span> <span class="s">&#34;puts RbConfig::CONFIG[&#39;libdir&#39;]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBS</span> <span class="o">=</span> <span class="o">-</span><span class="n">lpcre</span> <span class="o">-</span><span class="n">lgc</span> <span class="o">-</span><span class="n">lpthread</span> <span class="o">-</span><span class="n">levent</span> <span class="o">-</span><span class="n">lrt</span> <span class="o">-</span><span class="n">ldl</span>
</span></span><span class="line"><span class="cl"><span class="n">LDFLAGS</span> <span class="o">=</span> <span class="o">-</span><span class="n">rdynamic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">install</span><span class="p">:</span> <span class="n">all</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">all</span><span class="p">:</span> <span class="n">fast_blank</span><span class="p">.</span><span class="n">so</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fast_blank</span><span class="p">.</span><span class="nl">so</span><span class="p">:</span> <span class="n">fast_blank</span><span class="p">.</span><span class="n">o</span>
</span></span><span class="line"><span class="cl">  <span class="err">$</span><span class="p">(</span><span class="n">CC</span><span class="p">)</span> <span class="o">-</span><span class="n">shared</span> <span class="err">$</span><span class="o">^</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span> <span class="err">$</span><span class="p">(</span><span class="n">LIBCRYSTAL</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">LDFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">LIBS</span><span class="p">)</span> <span class="o">-</span><span class="n">L</span><span class="err">$</span><span class="p">(</span><span class="n">LIBRARY_PATH</span><span class="p">)</span> <span class="o">-</span><span class="n">L</span><span class="err">$</span><span class="p">(</span><span class="n">LIBRUBY</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fast_blank</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span> <span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">ext</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">fast_blank</span><span class="p">.</span><span class="n">cr</span>
</span></span><span class="line"><span class="cl">  <span class="n">crystal</span> <span class="n">build</span> <span class="o">--</span><span class="n">cross</span><span class="o">-</span><span class="n">compile</span> <span class="o">--</span><span class="n">release</span> <span class="o">--</span><span class="n">target</span> <span class="err">$</span><span class="p">(</span><span class="n">UNAME</span><span class="p">)</span> <span class="err">$</span><span class="o">&lt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nl">PHONY</span><span class="p">:</span> <span class="n">clean</span>
</span></span><span class="line"><span class="cl"><span class="nl">clean</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="n">bc_flags</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">o</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">so</span>
</span></span><span class="line"><span class="cl"><span class="n">endif</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ifeq</span> <span class="s">&#34;$(PLATFORM)&#34;</span> <span class="s">&#34;Darwin&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">CRYSTAL_FLAGS</span> <span class="o">=</span> <span class="o">-</span><span class="n">dynamic</span> <span class="o">-</span><span class="n">bundle</span> <span class="o">-</span><span class="n">Wl</span><span class="p">,</span><span class="o">-</span><span class="n">undefined</span><span class="p">,</span><span class="n">dynamic_lookup</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">install</span><span class="p">:</span> <span class="n">all</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">all</span><span class="p">:</span> <span class="n">fast_blank</span><span class="p">.</span><span class="n">bundle</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fast_blank</span><span class="p">.</span><span class="nl">bundle</span><span class="p">:</span> <span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">ext</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">fast_blank</span><span class="p">.</span><span class="n">cr</span>
</span></span><span class="line"><span class="cl">  <span class="n">crystal</span> <span class="err">$</span><span class="o">^</span> <span class="o">--</span><span class="n">release</span> <span class="o">--</span><span class="n">link</span><span class="o">-</span><span class="n">flags</span> <span class="s">&#34;$(CRYSTAL_FLAGS)&#34;</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">clean</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">log</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">o</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">bundle</span>
</span></span><span class="line"><span class="cl"><span class="n">endif</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A maioria das pessoas usando Crystal está em OS X, incluindo os criadores de Crystal. LLVM está sob o guarda-chuva da Apple, e todo o ecossistema deles depende muito de LLVM. Eles passaram muitos anos migrando primeiro o front-end de C, depois o back-end de C, do GCC padrão da GNU para o Clang. E conseguiram fazer tanto Objective-C quanto Swift compilarem para o IR do LLVM, e é por isso que ambos conseguem interagir nativamente.</p>
<p>Depois, eles melhoraram o suporte ao backend ARM, e é assim que eles conseguem ter um &ldquo;Simulador&rdquo; iOS inteiro (não um emulador lento como cachorro tipo o de Android), onde os apps iOS são compilados nativamente para rodar em processador Intel x86_64 durante o desenvolvimento e depois rapidamente recompilam para ARM quando vão empacotar para a App Store.</p>
<p>Dessa forma você consegue rodar nativamente, testar rapidamente, sem a lentidão de um ambiente emulado. A propósito, vou dizer isso uma vez: o maior erro do Google é não dar suporte a LLVM como deveria e ficar reinventando a roda. Se tivessem feito isso, Go já poderia ser usado para implementar para Android e Chromebooks, além de servidores baseados em x86, e poderiam jogar fora todo o imbróglio com Java/Oracle.</p>
<p>Mas estou divagando.</p>
<p>No OS X você pode passar uma link-flag &ldquo;<code>-bundle</code>&rdquo; para o crystal e ele provavelmente vai usar clang por baixo dos panos para gerar o bundle binário.</p>
<p>No Ubuntu, o crystal apenas compila para um arquivo objeto (.o) e você tem que invocar o GCC manualmente com a opção &ldquo;<code>-shared</code>&rdquo; para criar um shared object. Para fazer isso temos que usar o <a href="https://crystal-lang.org/docs/syntax_and_semantics/cross-compilation.html"target="_blank" rel="noopener">&quot;&ndash;cross-compile&quot;</a> e passar um triplet de target do LLVM para gerar o .o (isso requer a ferramenta <code>llvm-config</code>).</p>
<p>Shared Libraries (.so) e Loadable Modules (.bundle) são bichos diferentes, dê uma olhada <a href="http://docstore.mik.ua/orelly/unix3/mac/ch05_03.htm"target="_blank" rel="noopener">nessa documentação</a> para mais detalhes.</p>
<p>Tenha em mente que benchmarkar binários compilados com compiladores diferentes pode fazer diferença. Não sou expert mas, por puro empirismo, acredito que Ruby sob RVM no OS X é compilado usando o Clang padrão do OS X. No Ubuntu é compilado com GCC. Isso parece deixar Ruby no OS X &ldquo;levemente&rdquo; ineficiente em benchmarks sintéticos.</p>
<p>Por outro lado, binários Crystal linkados com GCC parecem &ldquo;levemente&rdquo; ineficientes no Ubuntu, enquanto Ruby no Ubuntu parece um pouco mais rápido, tendo sido compilado e linkado com GCC.</p>
<p>Então, quando comparamos Fast Blank/OS X/um pouco mais rápido com Ruby/OS X/mais lento contra Fast Blank/Ubuntu/um pouco mais lento com Ruby/Ubuntu/um pouco mais rápido, parece dar uma vantagem maior para a comparação no OS X em relação ao benchmark do Ubuntu, mesmo que os tempos individuais de computação não estejam tão longe entre si.</p>
<p>Vou voltar a esse ponto na seção de benchmarks.</p>
<p>Por fim, toda vez que você tem uma rubygem com extensão nativa, vai encontrar este trecho nos arquivos gemspec:</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">Gem</span><span class="o">::</span><span class="no">Specification</span><span class="o">.</span><span class="n">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s1">&#39;fast_blank&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span><span class="o">.</span><span class="n">extensions</span> <span class="o">=</span> <span class="o">[</span><span class="s1">&#39;ext/fast_blank/extconf.rb&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando a gem é instalada via <code>gem install</code> ou <code>bundle install</code>, ela vai rodar esse script para gerar um <code>Makefile</code> adequado. Em uma extensão pura em C, ele usa a biblioteca embutida &ldquo;mkmf&rdquo; para gerar.</p>
<p>No nosso caso, se temos Crystal instalado, queremos usar a versão Crystal, então adaptei o <code>extconf.rb</code> para ficar 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="nb">require</span> <span class="s1">&#39;mkmf&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;VERSION&#39;</span><span class="o">]</span> <span class="o">!=</span> <span class="s2">&#34;C&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">find_executable</span><span class="p">(</span><span class="s1">&#39;crystal&#39;</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">find_executable</span><span class="p">(</span><span class="s1">&#39;llvm-config&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Patch bem porco</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">create_makefile</span><span class="p">(</span><span class="n">target</span><span class="p">,</span> <span class="n">srcprefix</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mfile</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;Makefile&#34;</span><span class="p">,</span> <span class="s2">&#34;wb&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">cr_makefile</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="bp">__FILE__</span><span class="p">),</span> <span class="s2">&#34;../src/Makefile&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mfile</span><span class="o">.</span><span class="n">print</span> <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">cr_makefile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">ensure</span>
</span></span><span class="line"><span class="cl">    <span class="n">mfile</span><span class="o">.</span><span class="n">close</span> <span class="k">if</span> <span class="n">mfile</span>
</span></span><span class="line"><span class="cl">    <span class="nb">puts</span> <span class="s2">&#34;Crystal version of the Makefile copied&#34;</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></span><span class="line"><span class="cl"><span class="n">create_makefile</span> <span class="s1">&#39;fast_blank&#39;</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>Então, se ele encontrar <code>crystal</code> e <code>llvm-config</code> (que no OS X você precisa adicionar o path apropriado assim: <code>export PATH=$(brew --prefix llvm)/bin:$PATH</code>).</p>
<p>O <code>Rakefile</code> desse projeto declara a task padrão <code>:compile</code> como a primeira a rodar, e ela vai executar o <code>extconf.rb</code>, que vai gerar o <code>Makefile</code> apropriado e rodar o comando <code>make</code> para compilar e linkar a biblioteca correta no path <code>lib/</code>.</p>
<p>Então acabamos com <code>lib/fast_blank.bundle</code> no OS X e <code>lib/fast_blank.so</code> no Ubuntu. A partir daí podemos simplesmente fazer <code>require &quot;fast_blank&quot;</code> em qualquer arquivo Ruby da gem e ele terá acesso aos mapeamentos das funções C exportadas publicamente da biblioteca Crystal.</p>
<h3>Mapeando C-Ruby para Crystal<span class="hx:absolute hx:-mt-20" id="mapeando-c-ruby-para-crystal"></span>
    <a href="#mapeando-c-ruby-para-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Bom, qualquer extensão direta em C - sem FFI, fiddle ou outras &ldquo;pontes&rdquo; - vai SEMPRE ter uma vantagem muito maior.</p>
<p>A razão é que você literalmente tem que &ldquo;copiar&rdquo; dados de C-Ruby para Crystal/Rust/Go ou qualquer outra linguagem que você esteja fazendo binding. Já com uma extensão em C você consegue operar diretamente no espaço de memória com os dados, sem ter que mover ou copiar para outro lugar.</p>
<p>Por exemplo. Primeiro, você precisa fazer o binding das funções C de C-Ruby para Crystal. E fazemos isso com os mapeamentos do <a href="https://github.com/phoffer/crystalized_ruby/blob/master/src/lib_ruby.cr"target="_blank" rel="noopener">Crystalized Ruby do Paul Hoffer</a>. É um repositório experimental que ajudei a limpar um pouco para que ele depois extraísse essa biblioteca de mapeamento como uma Shard própria (shards são o mesmo que gems para Crystal). Por enquanto, eu simplesmente copiei o arquivo para o meu Fast Blank.</p>
<p>Algumas das partes relevantes são 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="n">lib</span> <span class="no">LibRuby</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="no">VALUE</span> <span class="o">=</span> <span class="no">Void</span><span class="o">*</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="no">METHOD_FUNC</span> <span class="o">=</span> <span class="no">VALUE</span> <span class="o">-&gt;</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="no">ID</span> <span class="o">=</span> <span class="no">Void</span><span class="o">*</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># strings</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_str_to_str</span><span class="p">(</span><span class="n">value</span> <span class="p">:</span> <span class="no">VALUE</span><span class="p">)</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_string_value_cstr</span><span class="p">(</span><span class="n">value_ptr</span> <span class="p">:</span> <span class="no">VALUE</span><span class="o">*</span><span class="p">)</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_str_new_cstr</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">)</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_utf8_encoding</span><span class="p">()</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_enc_str_new_cstr</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">,</span> <span class="n">enc</span> <span class="p">:</span> <span class="no">VALUE</span><span class="p">)</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># tratamento de exceções</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_rescue</span><span class="p">(</span><span class="n">func</span> <span class="p">:</span> <span class="no">VALUE</span> <span class="o">-&gt;</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">,</span> <span class="n">args</span> <span class="p">:</span> <span class="no">VALUE</span><span class="p">,</span> <span class="ss">callback</span><span class="p">:</span> <span class="no">VALUE</span> <span class="o">-&gt;</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">,</span> <span class="ss">value</span><span class="p">:</span> <span class="no">VALUE</span><span class="p">)</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">String</span>
</span></span><span class="line"><span class="cl">  <span class="no">RUBY_UTF</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_utf8_encoding</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">to_ruby</span>
</span></span><span class="line"><span class="cl">    <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_enc_str_new_cstr</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="no">RUBY_UTF</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></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">from_ruby</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">c_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_rescue</span><span class="p">(</span><span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">cr_str_from_rb_cstr</span><span class="p">,</span> <span class="n">str</span><span class="p">,</span> <span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">return_empty_string</span><span class="p">,</span> <span class="mi">0</span><span class="o">.</span><span class="n">to_ruby</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># FIXME ainda existe um problema não tratado: quando recebemos \u0000 do Ruby, ele lança &#34;string contains null bytes&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># então capturamos com rb_rescue, mas aí não conseguimos gerar um Pointer(UInt8) que represente o unicode 0, ao invés disso retornamos uma string em branco</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># mas aí os specs falham</span>
</span></span><span class="line"><span class="cl">    <span class="kp">new</span><span class="p">(</span><span class="n">c_str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">ensure</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">cr_str_from_rb_cstr</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">rb_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_to_str</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">c_str</span>  <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_string_value_cstr</span><span class="p">(</span><span class="n">pointerof</span><span class="p">(</span><span class="n">rb_str</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></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">return_empty_string</span><span class="p">(</span><span class="n">arg</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="n">_u8</span>
</span></span><span class="line"><span class="cl">    <span class="n">pointerof</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</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>Aí posso usar esses mapeamentos e helpers para construir uma classe &ldquo;Wrapper&rdquo; em Crystal:</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="s2">&#34;./lib_ruby&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;./string_extension&#34;</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">StringExtensionWrapper</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">blank?</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span> <span class="k">if</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_length</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span><span class="o">.</span><span class="n">blank?</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">rescue</span>
</span></span><span class="line"><span class="cl">    <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">blank_as?</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span> <span class="k">if</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_length</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span><span class="o">.</span><span class="n">blank_as?</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">rescue</span>
</span></span><span class="line"><span class="cl">    <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">crystal_value</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span><span class="o">.</span><span class="n">to_ruby</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></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 esse &ldquo;Wrapper&rdquo; depende da própria biblioteca &ldquo;pura&rdquo; em Crystal, como nos snippets das extensões da struct Char e da classe String que mostrei na primeira seção do artigo.</p>
<p>Por fim, tenho um arquivo principal &ldquo;fast_blank.cr&rdquo; que faz o extern dessas funções do Wrapper para que o C-Ruby possa enxergá-las como métodos comuns de String:</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="s2">&#34;./string_extension_wrapper.cr&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fun</span> <span class="n">init</span> <span class="o">=</span> <span class="no">Init_fast_blank</span>
</span></span><span class="line"><span class="cl">  <span class="no">GC</span><span class="o">.</span><span class="n">init</span>
</span></span><span class="line"><span class="cl">  <span class="no">LibCrystalMain</span><span class="o">.</span><span class="n">__crystal_main</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="no">Pointer</span><span class="p">(</span><span class="no">Pointer</span><span class="p">(</span><span class="no">UInt8</span><span class="p">))</span><span class="o">.</span><span class="n">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">string</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_define_class</span><span class="p">(</span><span class="s2">&#34;String&#34;</span><span class="p">,</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_cObject</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_define_method</span><span class="p">(</span><span class="n">string</span><span class="p">,</span> <span class="s2">&#34;blank?&#34;</span><span class="p">,</span> <span class="o">-&gt;</span><span class="no">StringExtensionWrapper</span><span class="o">.</span><span class="n">blank?</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_define_method</span><span class="p">(</span><span class="n">string</span><span class="p">,</span> <span class="s2">&#34;blank_as?&#34;</span><span class="p">,</span> <span class="o">-&gt;</span><span class="no">StringExtensionWrapper</span><span class="o">.</span><span class="n">blank_as?</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="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>Isso é basicamente boilerplate. Mas agora veja o que estou tendo que fazer no wrapper, neste snippet específico:</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="nc">self</span><span class="o">.</span><span class="nf">blank?</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span> <span class="k">if</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_length</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">  <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">str</span><span class="o">.</span><span class="n">blank?</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl"><span class="k">rescue</span>
</span></span><span class="line"><span class="cl">  <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</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>Estou recebendo uma String do C-Ruby tipada como ponteiro (VALUE), depois passo pelos mapeamentos do lib_ruby.cr para pegar os dados da string do C-Ruby e copiar para uma nova instância da representação interna de String do Crystal. Então, em qualquer momento eu tenho 2 cópias da mesma string, uma no espaço de memória do C-Ruby e outra no espaço de memória do Crystal.</p>
<p>Isso acontece com todas as extensões tipo FFI, mas não acontece com a implementação pura em C. Na implementação em C do Sam Saffron, ela trabalha diretamente com o mesmo endereço no espaço de memória do C-Ruby:</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-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">VALUE</span>
</span></span><span class="line"><span class="cl"><span class="nf">rb_str_blank</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">str</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">rb_encoding</span> <span class="o">*</span><span class="n">enc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="o">*</span><span class="n">s</span><span class="p">,</span> <span class="o">*</span><span class="n">e</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">enc</span> <span class="o">=</span> <span class="nf">STR_ENC_GET</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span> <span class="o">=</span> <span class="nf">RSTRING_PTR</span><span class="p">(</span><span class="n">str</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>Ela recebe um ponteiro (endereço de memória direto) e segue. E essa é uma vantagem enorme para a versão em C. Quando você tem um grande volume de strings de tamanho médio a grande sendo copiadas de C-Ruby para Crystal, isso adiciona um overhead perceptível que não dá para remover.</p>
<h3>Caveat do mapeamento de String<span class="hx:absolute hx:-mt-20" id="caveat-do-mapeamento-de-string"></span>
    <a href="#caveat-do-mapeamento-de-string" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ainda tenho um problema. Existe um edge case que não consegui superar ainda (ajuda é mais que bem-vinda). Quando o C-Ruby passa um unicode <code>&quot;\u0000&quot;</code>, eu não consigo criar o mesmo caractere em Crystal e acabo passando apenas uma string vazia (&quot;&quot;), o que não é a mesma coisa.</p>
<p>A maneira de lidar com isso é receber uma String Ruby (VALUE) e pegar a C-String dela 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="n">rb_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_to_str</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">c_str</span>  <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_string_value_cstr</span><span class="p">(</span><span class="n">pointerof</span><span class="p">(</span><span class="n">rb_str</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>Se a &ldquo;str&rdquo; for o &ldquo;\u0000&rdquo; (no Ruby 2.2.5 pelo menos), o C-Ruby lança uma exceção &ldquo;string contains null bytes&rdquo;. É por isso que eu faço rescue dessa exceção 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="n">c_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_rescue</span><span class="p">(</span><span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">cr_str_from_rb_cstr</span><span class="p">,</span> <span class="n">str</span><span class="p">,</span> <span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">return_empty_string</span><span class="p">,</span> <span class="mi">0</span><span class="o">.</span><span class="n">to_ruby</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>Quando uma exceção é disparada, tenho que passar o ponteiro para outra função para fazer o rescue:</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="nc">self</span><span class="o">.</span><span class="nf">return_empty_string</span><span class="p">(</span><span class="n">arg</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="n">_u8</span>
</span></span><span class="line"><span class="cl">  <span class="n">pointerof</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">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>Mas isso não está correto, estou apenas passando o ponteiro para um caractere &ldquo;0&rdquo;, que é &ldquo;vazio&rdquo;. Por isso, os specs não estão passando corretamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Failures:

  1) String provides a parity with active support function
     Failure/Error: expect(&#34;#{i.to_s(16)} #{c.blank_as?}&#34;).to eq(&#34;#{i.to_s(16)} #{c.blank2?}&#34;)

       expected: &#34;0 false&#34;
            got: &#34;0 true&#34;

       (compared using ==)
     # ./spec/fast_blank_spec.rb:22:in `block (3 levels) in &lt;top (required)&gt;&#39;
     # ./spec/fast_blank_spec.rb:19:in `times&#39;
     # ./spec/fast_blank_spec.rb:19:in `block (2 levels) in &lt;top (required)&gt;&#39;

  2) String treats  correctly
     Failure/Error: expect(&#34;\u0000&#34;.blank_as?).to be_falsey
       expected: falsey value
            got: true
     # ./spec/fast_blank_spec.rb:47:in `block (2 levels) in &lt;top (required)&gt;&#39;</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>O Ary deu uma dica simples depois, vou adicionar na conclusão abaixo.</p>
<h3>Os benchmarks sintéticos (cuidado em como interpretá-los!)<span class="hx:absolute hx:-mt-20" id="os-benchmarks-sintéticos-cuidado-em-como-interpretá-los"></span>
    <a href="#os-benchmarks-sint%c3%a9ticos-cuidado-em-como-interpret%c3%a1-los" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A <a href="https://github.com/rails/rails/blob/2a371368c91789a4d689d6a84eb20b238c37678a/activesupport/lib/active_support/core_ext/object/blank.rb#L101"target="_blank" rel="noopener">implementação original de String#blank? do ActiveSupport do Rails</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">String</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># 0x3000: fullwidth whitespace</span>
</span></span><span class="line"><span class="cl">  <span class="no">NON_WHITESPACE_REGEXP</span> <span class="o">=</span> <span class="s">%r![^\s#{[0x3000].pack(&#34;U&#34;)}]!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Uma string é blank se for vazia ou contiver só whitespace:</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34;&#34;.blank?                 # =&gt; true</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34;   &#34;.blank?              # =&gt; true</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34;　&#34;.blank?               # =&gt; true</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34; something here &#34;.blank? # =&gt; false</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">blank?</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 1.8 não trata [:space:] direito</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">encoding_aware?</span>
</span></span><span class="line"><span class="cl">      <span class="nb">self</span> <span class="o">!~</span> <span class="sr">/[^[:space:]]/</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="nb">self</span> <span class="o">!~</span> <span class="no">NON_WHITESPACE_REGEXP</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="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>É basicamente uma comparação por expressão regular, o que pode ser meio lento. A versão do Sam é um loop mais direto pela string para comparar cada caractere com o que é considerado &ldquo;blank&rdquo;. Existem vários codepoints unicode considerados blank, alguns não, e por isso as versões em C e Crystal são parecidas, mas diferentes da versão do Rails.</p>
<p>Na gem Fast Blank existe um script Ruby <code>benchmark</code> para comparar a extensão em C contra a implementação baseada em Regex do Rails.</p>
<p>A implementação Regex é chamada de <strong>&ldquo;Slow Blank&rdquo;</strong>. Ela é particularmente lenta se você passa uma String realmente vazia, então no benchmark o Sam adicionou uma <strong>&ldquo;New Slow Blank&rdquo;</strong> que checa primeiro com <code>String#empty?</code>, e essa versão é mais rápida nesse edge case.</p>
<p>A versão rápida em C se chama <strong>&ldquo;Fast Blank&rdquo;</strong>, mas mesmo que você possa considerá-la &ldquo;correta&rdquo;, ela não é compatível com todos os edge cases do Rails. Então ele implementou um <code>String#blank_as?</code> que é compatível com Rails. O Sam chama de <strong>&ldquo;Fast Activesupport&rdquo;</strong>.</p>
<p>Na minha versão em Crystal eu fiz a mesma coisa, tendo tanto <code>String#blank?</code> quanto <code>String#blank_as?</code>.</p>
<p>Então, sem mais delongas, aqui está o benchmark da <strong>versão em C no OS X</strong> para strings vazias, e exercitamos cada função muitas vezes em poucos segundos para ter resultados mais precisos (confira o <a href="https://github.com/evanphx/benchmark-ips"target="_blank" rel="noopener">&ldquo;benchmark/ips&rdquo;</a> do Evan Phoenix para entender a metodologia de &ldquo;iteration per second&rdquo;).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 0 ==================
Warming up _______________________________________
          Fast Blank   191.708k i/100ms
  Fast ActiveSupport   209.628k i/100ms
          Slow Blank    61.487k i/100ms
      New Slow Blank   203.165k i/100ms
Calculating _______________________________________
          Fast Blank     20.479M (± 9.3%) i/s -    101.414M in   5.001177s
  Fast ActiveSupport     21.883M (± 9.4%) i/s -    108.378M in   5.004350s
          Slow Blank      1.060M (± 4.7%) i/s -      5.288M in   5.001365s
      New Slow Blank     18.883M (± 6.9%) i/s -     94.065M in   5.008899s

Comparison:
  Fast ActiveSupport: 21882711.5 i/s
          Fast Blank: 20478961.5 i/s - same-ish: difference falls within error
      New Slow Blank: 18883442.2 i/s - same-ish: difference falls within error
          Slow Blank:  1059692.6 i/s - 20.65x slower</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>Está super rápido. A versão do Rails é 20x mais lenta na minha máquina.</p>
<p>Agora, <strong>versão Crystal no OS X</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 0 ==================
Warming up _______________________________________
          Fast Blank   174.349k i/100ms
  Fast ActiveSupport   174.035k i/100ms
          Slow Blank    64.684k i/100ms
      New Slow Blank   215.164k i/100ms
Calculating _______________________________________
          Fast Blank      8.647M (± 1.6%) i/s -     43.239M in   5.001530s
  Fast ActiveSupport      8.580M (± 1.3%) i/s -     42.987M in   5.010759s
          Slow Blank      1.047M (± 3.7%) i/s -      5.239M in   5.008907s
      New Slow Blank     19.090M (± 9.3%) i/s -     94.672M in   5.009057s

Comparison:
      New Slow Blank: 19090034.8 i/s
          Fast Blank:  8647459.7 i/s - 2.21x slower
  Fast ActiveSupport:  8580487.9 i/s - 2.22x slower
          Slow Blank:  1047465.3 i/s - 18.22x slower</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 expliquei antes, mesmo checando strings vazias, a versão Crystal é mais lenta que o check de Ruby por <code>String#empty?</code> (New Slow Blank), porque tenho a rotina de cópia de string nos mapeamentos do Wrapper. Isso adiciona overhead perceptível ao longo de muitas iterações. Continua sendo 18x mais rápido que Rails, mas perde para C-Ruby.</p>
<p>Por fim, <strong>versão Crystal no Ubuntu</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 0 ==================
Warming up _______________________________________
          Fast Blank   255.883k i/100ms
  Fast ActiveSupport   260.915k i/100ms
          Slow Blank   105.424k i/100ms
      New Slow Blank   284.670k i/100ms
Calculating _______________________________________
          Fast Blank      8.895M (± 9.8%) i/s -     44.268M in   5.037761s
  Fast ActiveSupport      8.647M (± 8.2%) i/s -     43.051M in   5.020125s
          Slow Blank      1.736M (± 3.9%) i/s -      8.750M in   5.048253s
      New Slow Blank     22.170M (± 6.2%) i/s -    110.452M in   5.004909s

Comparison:
      New Slow Blank: 22170031.0 i/s
          Fast Blank:  8895113.3 i/s - 2.49x slower
  Fast ActiveSupport:  8646940.8 i/s - 2.56x slower
          Slow Blank:  1736071.0 i/s - 12.77x slower</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>Note que está na mesma faixa, mas a versão Rails no Ubuntu roda quase duas vezes mais rápido comparada à equivalente no OS X, o que faz a comparação contra a biblioteca Crystal cair de 18x para 12x.</p>
<p>O benchmark continua comparando contra strings cada vez maiores, de 6, para 14, para 24, até 136 caracteres.</p>
<p>Vamos pegar só o último teste, com 136 caracteres. Primeiro com a <strong>versão em C no OS X</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 136 ==================
Warming up _______________________________________
          Fast Blank   177.521k i/100ms
  Fast ActiveSupport   193.559k i/100ms
          Slow Blank    89.378k i/100ms
      New Slow Blank    60.639k i/100ms
Calculating _______________________________________
          Fast Blank     10.727M (± 8.7%) i/s -     53.256M in   5.006538s
  Fast ActiveSupport     11.600M (± 8.3%) i/s -     57.681M in   5.009692s
          Slow Blank      1.872M (± 5.7%) i/s -      9.385M in   5.029243s
      New Slow Blank      1.017M (± 5.3%) i/s -      5.094M in   5.022994s

Comparison:
  Fast ActiveSupport: 11600112.2 i/s
          Fast Blank: 10726792.8 i/s - same-ish: difference falls within error
          Slow Blank:  1872262.5 i/s - 6.20x slower
      New Slow Blank:  1016926.7 i/s - 11.41x slower</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 versão em C é consistentemente muito mais rápida em todos os casos de teste e nos 136 caracteres ainda está 11x mais rápida que o Rails em Ruby puro.</p>
<p>Agora a <strong>versão Crystal no OS X</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 136 ==================
Warming up _______________________________________
          Fast Blank   127.749k i/100ms
  Fast ActiveSupport   126.538k i/100ms
          Slow Blank    94.390k i/100ms
      New Slow Blank    60.594k i/100ms
Calculating _______________________________________
          Fast Blank      3.283M (± 1.8%) i/s -     16.480M in   5.021364s
  Fast ActiveSupport      3.235M (± 1.3%) i/s -     16.197M in   5.008315s
          Slow Blank      1.888M (± 4.4%) i/s -      9.439M in   5.009458s
      New Slow Blank    967.950k (± 4.7%) i/s -      4.848M in   5.018946s

Comparison:
          Fast Blank:  3283025.1 i/s
  Fast ActiveSupport:  3234586.5 i/s - same-ish: difference falls within error
          Slow Blank:  1887800.5 i/s - 1.74x slower
      New Slow Blank:   967950.2 i/s - 3.39x slower</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>Também é mais rápida, mas só por 2 a 3 vezes comparada ao Ruby puro, bem longe do 11x. Mas minha hipótese é que o mapeamento e cópia de tantas strings adiciona um overhead grande que a versão em C não tem.</p>
<p>E a <strong>versão Crystal no Ubuntu</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 136 ==================
Warming up _______________________________________
          Fast Blank   186.810k i/100ms
  Fast ActiveSupport   187.306k i/100ms
          Slow Blank   143.439k i/100ms
      New Slow Blank    98.308k i/100ms
Calculating _______________________________________
          Fast Blank      3.517M (± 3.9%) i/s -     17.560M in   5.000791s
  Fast ActiveSupport      3.485M (± 3.8%) i/s -     17.419M in   5.006427s
          Slow Blank      2.755M (± 4.2%) i/s -     13.770M in   5.008490s
      New Slow Blank      1.551M (± 4.3%) i/s -      7.766M in   5.017853s

Comparison:
          Fast Blank:  3516960.7 i/s
  Fast ActiveSupport:  3484575.5 i/s - same-ish: difference falls within error
          Slow Blank:  2754669.0 i/s - 1.28x slower
      New Slow Blank:  1550815.2 i/s - 2.27x slower</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>Novamente, as versões Ubuntu tanto da biblioteca Crystal quanto do binário Ruby rodam mais rápido, e a comparação mostra no máximo o dobro de velocidade. E o <code>String#empty?</code> do Ruby puro está na mesma faixa que a versão Crystal.</p>
<h3>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></h3><p>A conclusão mais óbvia é que provavelmente errei ao escolher Fast Blank como minha primeira prova de conceito. O algoritmo é trivial demais e um simples check de <code>String#empty?</code> em Ruby puro é ordens de magnitude mais rápido que o overhead adicionado de mapear e copiar strings para Crystal.</p>
<p>Além disso, qualquer caso de uso onde você tem uma quantidade enorme de pequenos pedaços de dados sendo transferidos de C-Ruby para Crystal, ou qualquer extensão baseada em FFI, vai ter o overhead da cópia de dados, que uma versão pura em C não tem. É por isso que Fast Blank é melhor feito em C.</p>
<p>Outros casos de uso, onde você tem menos dados, ou dados que podem ser transferidos em lote (menos chamadas do C-Ruby para a extensão, com argumentos de tamanho maior, e com processamento mais custoso), são candidatos melhores para se beneficiar de extensões.</p>
<p>Mais uma vez, nem tudo fica automaticamente mais rápido, sempre temos que entender os cenários de uso primeiro. Mas como é muito mais fácil escrever em Crystal e fazer benchmark, podemos fazer provas de conceito mais rapidamente e descartar a ideia se as medições provarem que não vamos nos beneficiar tanto.</p>
<p>A documentação do Crystal recebeu recentemente um <a href="https://crystal-lang.org/docs/guides/performance.html"target="_blank" rel="noopener">&ldquo;Performance Guide&rdquo;</a>. É bem útil para você evitar armadilhas comuns que prejudicam a performance geral. Mesmo que LLVM seja bem competente em otimização pesada, ele não pode fazer tudo. Então leia tudo para melhorar suas habilidades gerais em Crystal.</p>
<p>Dito isso, ainda acredito que esse exercício valeu muito a pena. Provavelmente vou fazer mais alguns. Eu gostaria muito de agradecer ao Ary (criador do Crystal) e ao Paul Hoffer pela paciência em me ajudar com muitas das peculiaridades que encontrei pelo caminho.</p>
<p>Enquanto eu finalizava esse post, <a href="https://github.com/SamSaffron/fast_blank/pull/20#issuecomment-230875300"target="_blank" rel="noopener">o Ary apontou</a> que eu poderia provavelmente abandonar Strings completamente e trabalhar diretamente com um array de bytes, o que é uma boa ideia e provavelmente vou tentar. Acho que já deixei claro que toda a cópia de String adiciona um overhead bem perceptível, como vimos nos benchmarks acima. Me avise se alguém quiser contribuir também. Com mais alguns ajustes, acredito que dá para ter uma versão Crystal que pelo menos compita com a versão em C, sendo também mais legível e fácil de manter para a maioria dos rubyistas, que é o meu objetivo.</p>
<p>Espero que os códigos que publiquei aqui sirvam como exemplos de boilerplate para mais extensões Ruby baseadas em Crystal no futuro!</p>
]]></content:encoded><category>crystal</category><category>performance</category><category>algoritmos-e-estruturas-de-dados</category></item><item><title>Flertando com Crystal, a Perspectiva de um Rubyista</title><link>https://akitaonrails.github.io/2016/05/31/flertando-com-crystal-perspectiva-de-um-rubyista/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/05/31/flertando-com-crystal-perspectiva-de-um-rubyista/</guid><pubDate>Tue, 31 May 2016 18:21:00 -0300</pubDate><description>&lt;p&gt;Quem me acompanha sabe que ando me distraindo com &lt;a href="https://akitaonrails.github.io/elixir"&gt;Elixir&lt;/a&gt; ultimamente. Vou fazer uma introdução grande antes de mergulhar no Crystal, então tenham paciência.&lt;/p&gt;
&lt;p&gt;Vou logo dizer: o Erlang OTP e suas entranhas me intrigam mais do que apenas a novidade da sintaxe da linguagem. Acredito que se você quer construir sistemas altamente distribuídos e também altamente confiáveis, ou você vai usar Erlang (via Elixir), ou vai acabar replicando boa parte do que o Erlang já tem com OTP.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Quem me acompanha sabe que ando me distraindo com <a href="/elixir">Elixir</a> ultimamente. Vou fazer uma introdução grande antes de mergulhar no Crystal, então tenham paciência.</p>
<p>Vou logo dizer: o Erlang OTP e suas entranhas me intrigam mais do que apenas a novidade da sintaxe da linguagem. Acredito que se você quer construir sistemas altamente distribuídos e também altamente confiáveis, ou você vai usar Erlang (via Elixir), ou vai acabar replicando boa parte do que o Erlang já tem com OTP.</p>
<p>Isso fica claro quando você olha para os vários sistemas distribuídos baseados em Java/Scala como Hadoop, Spark, Cassandra, e percebe que todos eles teriam se beneficiado da arquitetura OTP do Erlang. Por outro lado, se você acompanha o Cassandra, vai ver que mesmo o Java ainda sofre para competir com C++, basta comparar com o clone chamado <a href="http://www.scylladb.com/"target="_blank" rel="noopener">ScyllaDB</a>.</p>
<p>Acredito que Elixir (talvez com HiPE) consegue competir na mesma liga do Java em sistemas distribuídos, evitando que as pessoas abandonem a plataforma Erlang em favor de C ou Java como <a href="https://www.infoq.com/news/2012/01/Katz-CouchDB-Couchbase-Server"target="_blank" rel="noopener">aconteceu com o CouchDB</a> lá em 2012, por falta de interesse e pela sintaxe exótica. E como o Java já tem um ecossistema gigante, Clojure entrou no jogo principalmente porque consegue interfacear diretamente com ele.</p>
<p>Vejo Go também como concorrente do Java, mas para outros casos de uso, particularmente no espaço de ferramentas de sistema onde você tem muito C/C++ e código de cola em Perl e Python. Um exemplo óbvio é a orquestração de Docker. Sim, dá pra fazer microsserviços, crawlers e outras coisas, mas eu não estou tão animado em construir grandes &ldquo;aplicações&rdquo; nele, embora nada impeça. De novo, é só opinião pessoal.</p>
<p>Rust é um forte concorrente para desenvolvimento de sistemas de baixo nível. Acho que ele pode substituir C em muitos casos de uso e ser usado para implementar bibliotecas e componentes a serem consumidos por outras linguagens, com o benefício adicional de ser uma linguagem moderna que tenta evitar problemas de segurança como o recente <a href="https://tonyarcieri.com/would-rust-have-prevented-heartbleed-another-look"target="_blank" rel="noopener">fiasco do Heartbleed</a>. Mas por causa do &ldquo;Borrow system&rdquo; eu acho ele extremamente burocrático, especialmente vindo de linguagens altamente dinâmicas como Ruby, Python ou mesmo Swift ou Elixir. Não estou dizendo que isso seja ruim, é só algo que leva muito mais tempo para ficar confortável do que eu esperava.</p>
<p>Crystal é algo entre Rust e Go. LLVM é algo que você precisa ter no radar, e por causa do <a href="http://appleinsider.com/articles/08/06/20/apples_other_open_secret_the_llvm_complier"target="_blank" rel="noopener">apoio da Apple</a> ao Swift está melhor do que nunca. Crystal é <em>similar</em> ao RubyMotion no sentido de que ambos são parecidos com Ruby mas não totalmente compatíveis, e ambos são front-end parsers para o LLVM. RubyMotion, por outro lado, está mais próximo do Ruby e até consegue usar algumas bibliotecas Ruby quase sem mudanças.</p>
<p>Você tem que tirar o chapéu para o Ary Borenszweig e seus contribuidores. É muito impressionante o quanto eles avançaram em tão pouco tempo, sem ter os bolsos fundos da Mozilla, Apple ou Google.</p>
<h3>Limitações<span class="hx:absolute hx:-mt-20" id="limitações"></span>
    <a href="#limita%c3%a7%c3%b5es" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Crystal toma bastante emprestado do Ruby, mas não é objetivo dele atingir qualquer nível de compatibilidade. Ele é uma linguagem fortemente e estaticamente tipada com Inferência de Tipos. Não tem nenhum componente de runtime como RubyMotion ou Swift, então não tem qualquer noção de introspecção ou reflexão sobre objetos.</p>
<p>Resumindo: você não tem &ldquo;#eval&rdquo; nem &ldquo;#send&rdquo;. A falta de avaliação em runtime acaba não sendo tão grave porque você tem manipulação de AST em tempo de compilação através de um sistema bom o bastante de Macros (mais sobre isso adiante).</p>
<p>Mas a falta de &ldquo;#send&rdquo; dói um pouco. É a única coisa que afasta o Crystal da flexibilidade dinâmica dos objetos do Ruby. Mas dá para entender por que isso não é prioridade.</p>
<p>Na versão 0.17.0, Crystal tem uma grande limitação: ele usa um <a href="http://crystal-lang.org/2013/12/05/garbage-collector.html"target="_blank" rel="noopener">garbage collector conservador Boehm-Demers-Weiser</a>.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr"><a href="https://twitter.com/asterite">@asterite</a> <a href="https://twitter.com/headius">@headius</a> I assumed as much... the Boehm GC, although handy for starting a new language, will only get you so far</p>&mdash; Jason Frey (@Fryguy9) <a href="https://twitter.com/Fryguy9/status/736990644082757632">May 29, 2016</a></blockquote>
<p>A linguagem está atualmente implementada como um processo single-threaded. Isso significa que você <em>provavelmente</em> não consegue usar 100% de todas as CPUs da sua máquina com um único processo. Posso estar enganado aqui, é só a minha primeira impressão.</p>
<p>Charles Nutter (do JRuby) faz um alerta sobre isso:</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr"><a href="https://twitter.com/Fryguy9">@fryguy9</a> From their site: <a href="https://t.co/uOjIT0c8ji">https://t.co/uOjIT0c8ji</a> It does say something about "when" parallelism happens. Risky to start single-threaded.</p>&mdash; Charles Nutter (@headius) <a href="https://twitter.com/headius/status/736982955239870464">May 29, 2016</a></blockquote>
<p>É uma faca de dois gumes. Usar um GC &ldquo;genérico&rdquo; plug-and-play como o Boehm - o que em si não é ruim, mas talvez não seja nem de longe tão poderoso quanto o conjunto de GCs de alta performance da própria JVM, como o novíssimo G1GC.</p>
<p>Fazer com que a linguagem seja single-threaded por padrão também significa que toda a biblioteca padrão e todo o ecossistema foi construído assumindo que não há paralelismo. Então não há race-conditions e, consequentemente, nenhum uso de sincronização adequada em lugar nenhum. Se um dia eles decidirem adicionar multi-threading, todo o código existente vai estar inseguro para threads. Provavelmente é isso que o Charles está alertando.</p>
<p>Também acho que não é um problema urgente. Você provavelmente não vai construir um sistema altamente paralelo com Crystal, mas talvez esse não seja o caso de uso. Para isso, você deveria estar usando Elixir + OTP, Scala + Akka, por exemplo.</p>
<h3>Concorrência<span class="hx:absolute hx:-mt-20" id="concorrência"></span>
    <a href="#concorr%c3%aancia" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Não ter acesso a Threads nativas não significa que você fica sem Concorrência. <a href="https://blog.golang.org/concurrency-is-not-parallelism"target="_blank" rel="noopener">Concorrência não é Paralelismo</a>. Desde a <a href="http://crystal-lang.org/2015/04/30/crystal-0.7.0-released.html"target="_blank" rel="noopener">versão 0.7.0</a> eles adicionaram suporte para I/O não-bloqueante e também Fibers e Channels.</p>
<p>Em poucas palavras, uma Fiber é um tipo especial de corotina. É um &ldquo;pedaço de execução&rdquo; que pode &ldquo;pausar&rdquo; a si mesmo e devolver o controle de execução para quem o chamou. Ele pode pausar a si mesmo, mas não pode ser pausado por fora como o Scheduler do Erlang consegue pausar seus processos em execução, por exemplo.</p>
<p>Nesse caso temos uma espécie de &ldquo;Fiber Scheduler&rdquo; que consegue &ldquo;retomar&rdquo; outras Fibers. Ele é, claro, atualmente single-threaded. E funciona como um Event Loop. Sempre que você tem uma operação de I/O não-bloqueante esperando leitura de arquivo, envio de pacote de rede, esse tipo de coisa, ele consegue devolver o controle para outras fibers retomarem o trabalho.</p>
<p>Isso faz com que ele funcione mais ou menos como o Node.js. Javascript também é uma linguagem single-threaded e o Node.js é basicamente uma implementação de event loop. Funções declaram outras funções anônimas como callbacks e tudo funciona com base em triggers de I/O para chamar de volta essas funções a cada tick do reactor.</p>
<p>Em cima disso você pode adicionar <a href="https://github.com/crystal-lang/crystal/issues/1967"target="_blank" rel="noopener">&ldquo;Channels&rdquo;</a> como popularizados pela linguagem Go do Google. Você pode iniciar quantos channels quiser. Aí você pode dar spawn em Fibers no Scheduler. As Fibers podem executar e ficar enviando mensagens pelos Channels. O controle de execução é cedido para quem estiver esperando receber dos mesmos Channels. Quando uma delas recebe e executa, o controle volta para o Scheduler, permitindo que outras Fibers executem, e assim ficam &ldquo;pingando&rdquo; e &ldquo;pongando&rdquo;.</p>
<p>Falando em ping-pong, tem <a href="https://gobyexample.com/channel-directions"target="_blank" rel="noopener">este snippet no site &ldquo;Go by Example&rdquo;</a>:</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-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">ping</span><span class="p">(</span><span class="nx">pings</span><span class="w"> </span><span class="kd">chan</span><span class="o">&lt;-</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">msg</span><span class="w"> </span><span class="kt">string</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="nx">pings</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">msg</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="kd">func</span><span class="w"> </span><span class="nf">pong</span><span class="p">(</span><span class="nx">pings</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">pongs</span><span class="w"> </span><span class="kd">chan</span><span class="o">&lt;-</span><span class="w"> </span><span class="kt">string</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="nx">msg</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">pings</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">pongs</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">msg</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="kd">func</span><span class="w"> </span><span class="nf">main</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="nx">pings</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">pongs</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">ping</span><span class="p">(</span><span class="nx">pings</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;passed message&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">pong</span><span class="p">(</span><span class="nx">pings</span><span class="p">,</span><span class="w"> </span><span class="nx">pongs</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="o">&lt;-</span><span class="nx">pongs</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 isso é basicamente a mesma coisa, mas implementada em Crystal:</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">ping</span><span class="p">(</span><span class="n">pings</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">pings</span><span class="o">.</span><span class="n">send</span> <span class="n">message</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pong</span><span class="p">(</span><span class="n">pings</span><span class="p">,</span> <span class="n">pongs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">message</span> <span class="o">=</span> <span class="n">pings</span><span class="o">.</span><span class="n">receive</span>
</span></span><span class="line"><span class="cl">  <span class="n">pongs</span><span class="o">.</span><span class="n">send</span> <span class="n">message</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">pings</span> <span class="o">=</span> <span class="no">Channel</span><span class="p">(</span><span class="nb">String</span><span class="p">)</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl"><span class="n">pongs</span> <span class="o">=</span> <span class="no">Channel</span><span class="p">(</span><span class="nb">String</span><span class="p">)</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl"><span class="n">spawn</span> <span class="n">ping</span> <span class="n">pings</span><span class="p">,</span> <span class="s2">&#34;passed message&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">spawn</span> <span class="n">pong</span> <span class="n">pings</span><span class="p">,</span> <span class="n">pongs</span>
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">pongs</span><span class="o">.</span><span class="n">receive</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;passed message&#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>Mesma coisa, mas mais agradável aos meus olhos, opinião pessoal de novo - o que é esperado se você também é um Rubyista.</p>
<p>Então, Crystal tem um Event Loop estilo Node.js/Javascript na forma de um Fiber Scheduler e um mecanismo de Channel/CSP estilo Go para coordenar processamento concorrente. É um mecanismo de &ldquo;multitarefa cooperativa&rdquo;. Não é bom para processamento intensivo de CPU, como cálculos pesados ou processamento massivo de dados. Funciona melhor para processamento intensivo de I/O.</p>
<p>Se você tem uma Fiber fazendo processamento super pesado, ela vai bloquear o Scheduler, igual acontece no Node.js. A implementação atual do Scheduler está atrelada a uma única thread nativa, então por enquanto ela não consegue usar outras threads nativas disponíveis no sistema.</p>
<p>Como a implementação dos Channels é bem nova, ela ainda não é usada por toda a biblioteca padrão. Mas a implementação padrão do <code>HTTP::Server</code> usa Fibers. E como ele é compilado para um binário nativo, é <strong>muito mais rápido</strong> que Javascript, como mostra este <a href="https://github.com/sdogruyol/fast-http-server"target="_blank" rel="noopener">&ldquo;fast-http-server&rdquo;</a>:</p>
<ul>
<li>fast-http-server (Crystal)  18348.47rpm a 8.67ms de tempo médio de resposta</li>
<li>http-server (Node.js)   2105.55rpm a 47.92ms de tempo médio de resposta</li>
<li>SimpleHTTPServer (Python)   785.14rpm a 1.91ms de tempo médio de resposta</li>
</ul>
<p>Estamos falando de uma ordem de magnitude de 8 vezes mais rápido que Node.js e mais de 20 vezes mais rápido que Python.</p>
<p>Como sempre, você não deve confiar cegamente em <a href="https://github.com/kostya/benchmarks"target="_blank" rel="noopener">benchmarks sintéticos</a>, mas dependendo do tipo de processamento que você decida comparar, você verá Crystal bem no mesmo patamar de Rust, Go, Swift, D.</p>
<h3>Inferência de Tipos<span class="hx:absolute hx:-mt-20" id="inferência-de-tipos"></span>
    <a href="#infer%c3%aancia-de-tipos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Crystal é uma linguagem estaticamente tipada. Mas ela não vai exigir que você declare cada tipo de antemão. Ela é bem inteligente na Inferência de Tipos, então se você só digitar:</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">a</span> <span class="o">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="n">b</span> <span class="o">=</span> <span class="s2">&#34;HELO&#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>Ela vai saber que &ldquo;a&rdquo; é um &ldquo;Int32&rdquo; e que &ldquo;b&rdquo; é uma &ldquo;String&rdquo;. A propósito, ao contrário do Ruby, todas as Strings precisam usar aspas duplas. Mas a coisa fica particularmente mais complicada quando você está lidando com estruturas de dados complexas como JSON.</p>
<p>Em Ruby eu posso simplesmente parsear uma String JSON e imediatamente explorar sua estrutura 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="n">parsed_json</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">body</span><span class="p">)</span><span class="o">[</span><span class="s2">&#34;files&#34;</span><span class="o">].</span><span class="n">first</span><span class="o">[</span><span class="s2">&#34;id&#34;</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>
<p>Não dá para fazer isso em Crystal, ao invés a <a href="http://crystal-lang.org/api/JSON.html#mapping%28properties%2Cstrict%3Dfalse%29-macro"target="_blank" rel="noopener">abordagem recomendada</a> é declarar uma estrutura tipo schema 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">class</span> <span class="nc">FilesResponse</span>
</span></span><span class="line"><span class="cl">  <span class="no">JSON</span><span class="o">.</span><span class="n">mapping</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="ss">ok</span><span class="p">:</span> <span class="p">{</span><span class="ss">type</span><span class="p">:</span> <span class="no">Bool</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="ss">files</span><span class="p">:</span> <span class="p">{</span><span class="ss">type</span><span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="no">FilesItemResponse</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">FilesItemResponse</span>
</span></span><span class="line"><span class="cl">  <span class="no">JSON</span><span class="o">.</span><span class="n">mapping</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span><span class="p">:</span> <span class="p">{</span><span class="ss">type</span><span class="p">:</span> <span class="nb">String</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nb">name</span><span class="p">:</span> <span class="p">{</span><span class="ss">type</span><span class="p">:</span> <span class="nb">String</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="ss">filetype</span><span class="p">:</span> <span class="p">{</span><span class="ss">type</span><span class="p">:</span> <span class="nb">String</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="ss">created</span><span class="p">:</span> <span class="p">{</span><span class="ss">type</span><span class="p">:</span> <span class="no">Time</span><span class="p">,</span> <span class="ss">converter</span><span class="p">:</span> <span class="no">Time</span><span class="o">::</span><span class="no">EpochConverter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="ss">timestamp</span><span class="p">:</span> <span class="p">{</span><span class="ss">type</span><span class="p">:</span> <span class="no">Time</span><span class="p">,</span> <span class="ss">converter</span><span class="p">:</span> <span class="no">Time</span><span class="o">::</span><span class="no">EpochConverter</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="ss">permalink</span><span class="p">:</span> <span class="p">{</span><span class="ss">type</span><span class="p">:</span> <span class="nb">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="k">end</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="c1"># parsed_json = JSON.parse(response.body)[&#34;files&#34;].first[&#34;id&#34;]</span>
</span></span><span class="line"><span class="cl"><span class="n">parsed_json</span> <span class="o">=</span> <span class="no">FilesResponse</span><span class="o">.</span><span class="n">from_json</span><span class="p">(</span><span class="vi">@body</span><span class="p">)</span><span class="o">.</span><span class="n">files</span><span class="o">.</span><span class="n">first</span><span class="o">.</span><span class="n">id</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 sou muito acostumado com o duck typing do Ruby e a habilidade de consultar objetos em runtime. Em Crystal a coisa é diferente, e pode ficar bem cansativo mudar seu mindset para pensar em tipos de antemão. O compilador vai gritar muito durante seu ciclo de desenvolvimento até você se acostumar com esse conceito.</p>
<p>Fiz um experimento com parsing de JSON e o resultado é um projeto que chamei de <a href="https://github.com/akitaonrails/cr_slack_cleanup"target="_blank" rel="noopener">&ldquo;cr_slack_cleanup&rdquo;</a>, que mostra tanto essa ideia de Schemas JSON quanto Fibers e Channels como expliquei na seção anterior.</p>
<p>Update: depois que postei este artigo o @LuisLavena entrou em campo para me corrigir: dá para fazer como em Ruby, sem schemas:</p>
<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="en" dir="ltr"><a href="https://twitter.com/AkitaOnRails">@AkitaOnRails</a> <a href="https://twitter.com/CrystalLanguage">@CrystalLanguage</a> re: JSON, you can parse it kinda-like Ruby, see JSON::Any: <a href="https://t.co/MD7PYy5AVH">https://t.co/MD7PYy5AVH</a></p>&mdash; Luis Lavena (@luislavena) <a href="https://twitter.com/luislavena/status/738005493189316608">June 1, 2016</a></blockquote>
<p>E fica 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="nb">require</span> <span class="s2">&#34;json&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="s">&lt;&lt;-JSON
</span></span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;files&#34;</span><span class="p">:</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;photo.jpg&#34;</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="s2">&#34;id&#34;</span><span class="p">:</span> <span class="mi">99</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;another.jpg&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="no">JSON</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">obj</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">obj</span><span class="o">[</span><span class="s2">&#34;files&#34;</span><span class="o">].</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">item</span><span class="o">[</span><span class="s2">&#34;id&#34;</span><span class="o">].</span><span class="n">as_i</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>Então, parsing de JSON pode ser quase idêntico ao que você faria em Ruby, embora ele também recomende Schemas porque acaba sendo um pouco mais rápido.</p>
<h3>Macros<span class="hx:absolute hx:-mt-20" id="macros"></span>
    <a href="#macros" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A última feature importante que vale mencionar é a presença de Macros. Como você não tem controle do seu código em runtime, não tem &ldquo;eval&rdquo;, não tem &ldquo;send&rdquo;, então você ficaria sem como fazer metaprogramação de verdade.</p>
<p>Macros trazem de volta um pouco da metaprogramação do Ruby. Por exemplo, em Ruby, quando você inclui um Module em uma Class, existe o hook &ldquo;included?&rdquo; no Module onde você pode adicionar metaprogramação como coisas com &ldquo;class_eval&rdquo;.</p>
<p>Em Crystal, um Module também tem o hook &ldquo;included&rdquo; mas agora é uma Macro. Você pode fazer 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">module</span> <span class="nn">Foo</span>
</span></span><span class="line"><span class="cl">  <span class="n">macro</span> <span class="n">included</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">%</span> <span class="k">if</span> <span class="vi">@type</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">&#34;Bar&#34;</span> <span class="s">%}
</span></span></span><span class="line"><span class="cl"><span class="s">    def self.hello
</span></span></span><span class="line"><span class="cl"><span class="s">      puts &#34;Bar&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">    end
</span></span></span><span class="line"><span class="cl"><span class="s">    {% else %}</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">hello</span>
</span></span><span class="line"><span class="cl">      <span class="nb">puts</span> <span class="s2">&#34;HELLO&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="o">%</span> <span class="k">end</span> <span class="o">%</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></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Bar</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Foo</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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">Something</span>
</span></span><span class="line"><span class="cl">  <span class="kp">include</span> <span class="no">Foo</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">Bar</span><span class="o">.</span><span class="n">hello</span> <span class="c1"># =&gt; &#34;Bar&#34;</span>
</span></span><span class="line"><span class="cl"><span class="no">Something</span><span class="o">.</span><span class="n">hello</span> <span class="c1"># =&gt; &#34;HELLO&#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>É como ter templates &ldquo;ERB&rdquo; para código. Uma macro é código construindo código em tempo de compilação. Na AST resultante é como se você tivesse escrito o código repetitivo e chato na mão. O binário nativo compilado não se importa. Se você vem do C, é como pré-processamento, mas com controle sobre uma árvore AST de Nodes ao invés de só manipular código-fonte. Você até consegue fazer algo parecido com o venerado &ldquo;#method_missing&rdquo;.</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">class</span> <span class="nc">Foo</span>
</span></span><span class="line"><span class="cl">  <span class="n">macro</span> <span class="nb">method_missing</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">block</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{{</span><span class="n">pp</span> <span class="nb">name</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></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">hello_world</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; name = &#34;hello_world&#34;</span>
</span></span><span class="line"><span class="cl"><span class="no">Foo</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">bla_bla</span><span class="p">(</span><span class="s2">&#34;bla&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; name = &#34;bla_bla&#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>Qualquer macro pode receber uma AST como argumento e você pode manipular essa AST como bem entender. Por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>module Foo
  macro teste(ast)
    puts {{ast.stringify}}
  end
end

Foo.teste &#34;Hello World&#34;.split(&#34; &#34;).join(&#34; - &#34;)
# =&gt; (&#34;Hello World&#34;.split(&#34; &#34;)).join(&#34; - &#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>Nesse exemplo você só pega uma versão normalizada do código que passou como argumento. E o código não vai ser &ldquo;executado&rdquo; naquela posição.</p>
<p>Então, você pode definir novos métodos e chamar versões diferentes de métodos dependendo da combinação de elementos que você pega da AST. Um exemplo do que é possível é este experimento do desenvolvedor Oleksii que empacotei neste pequeno projeto que chamei de <a href="https://github.com/akitaonrails/cr_chainable_methods"target="_blank" rel="noopener">&ldquo;cr_chainable_methods&rdquo;</a>, e a gente consegue construir código bem diferente tanto de Crystal quanto de Ruby:</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">result</span> <span class="o">=</span> <span class="n">pipe</span> <span class="s2">&#34;Hello World&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">&gt;&gt;</span> <span class="no">Foo</span><span class="o">.</span><span class="n">split_words</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">&gt;&gt;</span> <span class="no">Foo</span><span class="o">.</span><span class="n">append_message</span><span class="p">(</span><span class="s2">&#34;Bar&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">&gt;&gt;</span> <span class="no">Bar</span><span class="o">.</span><span class="n">add_something</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">&gt;&gt;</span> <span class="no">Foo</span><span class="o">.</span><span class="n">join</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">&gt;&gt;</span> <span class="n">unwrap</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>(Sim, um subconjunto do que um Pipe de verdade do Elixir faz.)</p>
<h3>Crystal Shards<span class="hx:absolute hx:-mt-20" id="crystal-shards"></span>
    <a href="#crystal-shards" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Por fim, um aspecto que eu gosto no Crystal é que ele já tem gerenciamento de tarefas e gerenciamento de dependências resolvidos. Sem discussões sem fim sobre qual implementação de pacotes e dependências a gente &ldquo;deveria&rdquo; usar.</p>
<p>Você pode simplesmente escrever um script simples num arquivo como &ldquo;test.cr&rdquo; e rodar esse arquivo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>crystal test.cr</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 você pode buildar e compilar para um binário nativo antes de executar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>crystal build test.cr --release
./test</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 também dá para começar com uma estrutura de projeto adequada e ciclo de desenvolvimento assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>crystal init app test
cd test
crystal build src/test.cr --release
./test
git add .
git commit -m &#34;initial commit&#34;
git push -u origin master</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>Ele monta uma estrutura de diretórios de projeto adequada, um repositório Git já vem pré-inicializado para você (por que diabos você programaria sem Git!?) com um arquivo &ldquo;.gitignore&rdquo; decente.</p>
<p>Você também vai encontrar um arquivo &ldquo;shard.yml&rdquo; onde você declara o nome da sua aplicação ou biblioteca, versão, autor, e também as suas dependências assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">kemal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">0.12.0</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="nt">dependencies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">radix</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">github</span><span class="p">:</span><span class="w"> </span><span class="l">luislavena/radix</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">0.3.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kilt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">github</span><span class="p">:</span><span class="w"> </span><span class="l">jeromegn/kilt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">0.3.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">author</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">Serdar Dogruyol &lt;dogruyolserdar@gmail.com&gt;</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 exemplo é do <a href="https://github.com/sdogruyol/kemal"target="_blank" rel="noopener">framework web Kemal</a> do Serdar. Ele depende das bibliotecas Radix e Kilt. Você precisa rodar <code>crystal deps</code> e ele vai buscar do Github antes de você poder compilar.</p>
<p>E mais: todo projeto vem com um diretório &ldquo;spec&rdquo; adequado onde você pode escrever testes no estilo Rspec e rodá-los usando <code>crystal spec</code>. E o resultado do runner de specs vai ser parecido com isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ crystal spec
........................................................

Finished in 8.28 milliseconds
56 examples, 0 failures, 0 errors, 0 pending</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>Tudo embutido, sem precisar de bibliotecas externas.</p>
<p>E 8 milissegundos para rodar a suíte de testes inteira de um micro framework web? Nada mau. Se você somar o tempo de inicialização do executável, ainda dá em torno de 1 segundo de tempo total de execução. Bem impressionante.</p>
<h3>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></h3><p>Crystal não tem como objetivo ser um substituto do Ruby como o JRuby. Então ele nunca vai rodar projetos Ruby existentes e complicados como Ruby on Rails ou mesmo Sinatra.</p>
<p>Para mim, Crystal preenche uma lacuna em alguns casos de uso onde eu usaria Rust ou Go. Ele tem o desempenho bruto de um binário nativo otimizado por LLVM, um sistema de concorrência razoavelmente rápido no estilo Go (e muito mais rápido e menos intensivo em recursos que Node.js), com o benefício adicional de uma sintaxe muito mais agradável estilo Ruby.</p>
<p>Você pode até começar a experimentar Crystal para construir extensões nativas Ruby sem precisar de FFI e sem ter que escrever C. Existe uma tentativa de <a href="https://github.com/startram/activesupport.cr"target="_blank" rel="noopener">port do ActiveSupport</a> como uma extensão nativa para o MRI Ruby, escrita como prova de conceito em Crystal.</p>
<p>Se você é Rubyista vai se sentir bastante em casa com a maior parte da biblioteca padrão. Vai sentir falta de uma coisa ou outra, mas na maior parte do tempo vai parecer Ruby. O <a href="http://crystal-lang.org/docs"target="_blank" rel="noopener">Guia oficial</a> é bom o suficiente para começar e tem uma <a href="http://crystal-lang.org/api"target="_blank" rel="noopener">documentação de API</a> abrangente, suficiente para checar se aquela API favorita do Ruby está lá ou não, e se não estiver, quais são os substitutos. Na maioria das vezes você vai pensar <em>&ldquo;hmm, em Ruby eu usaria Enumerator#each &hellip; e sim, com certeza, tem Enumerator#each como esperado.&rdquo;</em> A documentação da API está precisando de mais carinho, então se você quer contribuir, esse é um bom lugar para começar.</p>
<p>Se você quer dar uma olhada em uma lista curada de bibliotecas e aplicações Crystal, vá direto no <a href="http://awesome-crystal.com/"target="_blank" rel="noopener">&ldquo;Awesome Crystal&rdquo;</a>. Você também pode ver os mais dinâmicos <a href="https://github.com/trending/crystal"target="_blank" rel="noopener">&ldquo;Trending Projects in Github&rdquo;</a>.</p>
<p>Na maior parte, sua proficiência em Ruby vai compensar para construir sistemas pequenos onde performance bruta e concorrência sejam mesmo necessárias. Esse é exatamente o caso de uso para o port do <a href="https://github.com/mperham/sidekiq.cr"target="_blank" rel="noopener">Sidekiq.cr</a> do Mike Perham. Imagine que você tem gigabytes de arquivos para processar. Ou você tem milhares de sites externos para crawlear, parsear e organizar. Ou você tem petabytes de dados no seu banco esperando para serem processados. Essas são tarefas em que MRI Ruby não vai te ajudar, mas Crystal pode muito bem ser a resposta rápida.</p>
<p>Você até pode pegar um dos vários micro frameworks web como o Kemal e fazer deploy dos seus provedores de API HTTP <a href="http://crystal-lang.org/2016/05/26/heroku-buildpack.html"target="_blank" rel="noopener">direto no Heroku</a> agora. Isso te dá níveis de performance estilo Go, e isso é <strong>muito</strong> atraente para mim, pois também detesto a sintaxe arcaica do Go.</p>
<p>Então, Rust, Go, Javascript, todas muito rápidas, mas com sintaxes bem questionáveis e nem tão agradáveis. Elas são bem mais maduras, e seus ecossistemas são muito maiores. Mas, a menos que eu realmente precise, prefiro escolher Crystal para os casos de uso que descrevi acima, e acho que você também vai encontrar bons usos para ele.</p>
<p>Update: depois que postei, o criador do Crystal, @Asterite, teve algumas coisas a acrescentar também:</p>
<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="en" dir="ltr"><a href="https://twitter.com/AkitaOnRails">@AkitaOnRails</a> <a href="https://twitter.com/waj">@waj</a> so it&#39;s likely that we&#39;ll have something similar. We aren&#39;t there yet because we are not Google, Mozilla nor Apple</p>&mdash; Ary Borenszweig (@asterite) <a href="https://twitter.com/asterite/status/737778080085868545">May 31, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
]]></content:encoded><category>crystal</category><category>ruby</category><category>linguagens-de-programacao</category></item></channel></rss>