Existem Linguagens Melhores pra programar com LLMs?

11 de outubro de 2026 · 💬 Participe da Discussão
Se tem preguiça de ler, clique aqui pro TL;DR

Semana passada o DHH resolveu farmar engajamento com o formato mais preguiçoso que existe: converteu o Campfire, o app de chat dele, pra várias linguagens usando agentes de IA, e foi soltando os resultados de throughput um a um no X. Na série que ele postou, a versão em Rust saiu na frente das equivalentes em Elixir e Ruby. A thread inteira foi desenhada pra provocar, e provocou.

Não vou defender ele dessa vez. Uma conversão única, feita por agente, sem ajuste fino nenhum, não mede linguagem: mede o quanto o modelo conhece os idiomas de cada stack. É rage bait e ele sabe. Dito isso, eu faço o mesmo de vez em quando, e metade dos críticos dele também, então somos todos hipócritas e segue o jogo.

E o ponto fraco da metodologia dele virou tópico por conta própria: várias pessoas notaram que a versão em Rust tinha passado por bem mais iterações de agente que as outras, ou seja, mais rodadas de otimização. O @kurtome respondeu na prática: pegou a versão em Elixir e subiu as requisições da página principal em 10x, com otimizações óbvias que nenhuma primeira conversão faz, tipo cache e pooling assíncrono de conexões de banco.

A comparação dele, então, não era “Rust contra Elixir”: era “Rust depois de N rodadas de ajuste contra Elixir depois de uma”. E isso escancara a verdade que a thread finge ignorar: nenhum software de verdade sai com meia dúzia de prompts. Codar com IA brilha quando um engenheiro experiente pilota o agente rumo ao objetivo dele, e isso significa dezenas de iterações, não três.

A parte boa é que a isca foi útil pra mim. Eu venho fazendo benchmarks de LLM e benchmark de modelos de decisão, mas nunca tinha medido, de forma reproduzível, a pergunta que essa thread fingia responder: faz diferença qual linguagem você escolhe quando quem escreve o código é um LLM? Minha resposta curta já estava dada em dois posts meus:

Não existe linguagem melhor, existe ferramenta certa

Aquela lista do post não é ranking, é mapa de terreno. Cada linguagem e cada framework foram desenhados pra um caso de uso, e nenhum cobre todos. Isso valia quando você escrevia cada linha na mão e continua valendo com agente. O LLM não muda a física do problema: ele só muda quem digita.

E o mapa vai além da linguagem. Sistema real tem infraestrutura em volta: Kubernetes quando a escala pede, SQLite por padrão porque a maioria dos apps nunca precisa de mais que isso, Postgres com pgBouncer quando precisa, Redis pra cache e chave-valor, RabbitMQ ou NATS pra fila. Escolher “a stack” no dia zero e achar que ela dura dez anos é fantasia. Não dura, e tudo bem.

Exemplo pessoal: web app novo, eu abro Ruby on Rails sem pensar duas vezes. É a solução mais completa de ponta a ponta que existe pra web, e com LLM escrevendo a maior parte do código, a produtividade do framework pesa ainda mais que a da linguagem. Conforme o sistema cresce, eu vou trocando as partes que pedem outra ferramenta: fila de alto volume sai do lado do Rails e vai pra algo da terra Erlang, começando por RabbitMQ, que já resolve a parte difícil de backpressure pra mim.

Nenhum software bom está pronto: ele cresce. O uso dita o que você substitui, reescreve e adiciona. A arquitetura final nunca é a do primeiro commit, e quem tenta acertar tudo no primeiro commit só gera documento morto.

A superstição dos tokens

A thread do DHH ressuscitou duas crenças que circulam desde que LLM virou ferramenta de trabalho:

  • “Linguagem de script é melhor pra IA porque a sintaxe enxuta gasta menos tokens.”
  • “Linguagem fortemente tipada é melhor pra IA porque o compilador pega os erros, então precisa de menos teste e menos reparo.”

As duas soam razoáveis e as duas são superstição até alguém medir. Eu apostava contra as duas, por experiência: compilador bom ajuda, mas não faz mágica, e concisão de sintaxe nunca foi o gargalo de um agente que lê e escreve arquivo o dia inteiro.

O que realmente separa projeto que anda de projeto que gira em círculo é disciplina de teste. Já escrevi que TDD virou obrigação técnica, não filosofia, e o benchmark deste texto é mais um dado na mesma direção. Não importa se você faz test-first ou test-after: importa que a suíte cresça junto com o código, que todo bug reportado por usuário vire teste de regressão antes da correção, e que você nunca repita o mesmo erro duas vezes. Software sem usuário é software morto, então release cedo, release sempre, quebra rápido e conserta mais rápido ainda. IA fecha exatamente essa lacuna: o ciclo de reproduzir, testar e corrigir ficou barato. QA virou a habilidade mais valiosa do programador, muito acima de “saber a sintaxe de Rust”.

Disciplina vence compilador e vence linguagem. Essa era a minha hipótese. Foi medir.

O benchmark

Montei um experimento reproduzível, público no repositório vibe-coding-benchmarks, com os resultados por tentativa publicados em JSON e os rastros brutos fora do repo por contenção de credenciais. Não é um campeonato de linguagem, e o próprio design repete isso: é uma fotografia de uma tarefa real, medindo o que dá pra medir com honestidade.

A tarefa

Uma tarefa só, de propósito: manter um motor de livro-razão (ledger) multilocatário. O agente recebe um código v1 funcionando e precisa levá-lo a um contrato v2, com identidades por tenant, autorização, snapshots serializáveis, índice secundário único com troca de alias, journal com limite de capacidade, recuperação de grupos rasgados, checkpoint, migração de schema legado e IDs de retry duráveis. Não é FizzBuzz: é o tipo de sistema que exige invariante atravessando operações, exatamente onde LLM costuma tropeçar.

A correção é julgada por 43 histórias de teste aplicadas depois da coleta, fora da vista do agente: as 27 públicas do T1 mais 16 ocultas. Passar é 43 de 43. As condições:

  • T0: contrato em prosa, exemplos de entrada e saída, um launcher funcionando. Sem suíte de testes.
  • T1: o mesmo, mais 27 histórias públicas e um driver pra rodá-las.

Cada tentativa tinha 15 minutos de orçamento, com no máximo duas continuações de 3 minutos. Na prática, nenhuma das 112 precisou de continuação.

As linguagens e os modelos

Sete linguagens em três estratos, com os toolchains pinados e o Rust em modo seguro:

  • Nativas tipadas: Rust (safe, pinado), Haskell (GHC pinado).
  • VM tipadas: C# (com nullable analysis), Kotlin, TypeScript (strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes).
  • Dinâmicas de script: Python e Ruby, sem checker nenhum.

Quatro configurações de modelo, todas por assinatura, sem fallback pago: Kimi K3 (high), GLM 5.3 via OpenCode (high), Claude Opus 5.5 (medium) e Codex com gpt-6 Sol (medium). Cruzando tudo: 7 linguagens × 4 modelos × 2 condições × 2 repetições = 112 tentativas.

O que esse design não mede

Antes dos números, os freios, porque eles fazem parte do resultado. É uma tarefa só, com duas repetições por célula: amostra pequena por construção. Cobertura de tokens é parcial em alguns modelos (dado faltante não é zero). O harness mudou no meio da campanha em três épocas de instrumentação. E atrasos de provedor contaminam tempo de parede: uma das tentativas do Opus passou metade do relógio esperando liberação de cota. Todos os números abaixo são descritivos, e eu aponto onde eles não sustentam conclusão.

Tem um freio adicional, de propósito: o nível de iteração. Meu desenho mede o que dá pra alcançar em todas as linguagens com níveis parecidos de iteração, em orçamento curto, sem um humano ajustando depois. É fotografia do piso, não do teto. Qualquer uma das 112 implementações provavelmente melhora com mais rodadas de agente, e as quatro stacks de fila idem: o que eu publico é o que cada linguagem entrega “de fábrica” sob o mesmo esforço, não o máximo que um engenheiro paciente extrairia dela.

Disclaimer obrigatório antes de citar qualquer número deste texto por aí: todos os resultados a seguir valem pra este benchmark, com esta tarefa, estes modelos, estes prompts e este hardware. O seu caso pode variar violentamente. Se a sua tarefa não se parece com um motor de livro-razão, se o seu modelo não é um destes quatro, se a sua fila não é a minha fila, os números não se transferem. O código, as tarefas, as notas e o passo a passo pra reproduzir estão todos no repositório no GitHub. E se você discordar de algum resultado e essa discordância for importante pra sua empresa ou pro seu projeto, a única atitude honesta é a que eu tive: pare de argumentar e rode o seu próprio benchmark.

Resultado 1: em acerto, os modelos de fronteira empataram no teto

ConfiguraçãoCorretas no prazoCorretas no limiteFalhasSem nota
Kimi K3 high17722
GLM 5.3 high (OpenCode)7975
Opus 5.5 medium2503 (interrupção de infra)0
Codex / Sol medium28000

Leitura: Sol passou nas 28 tentativas elegíveis e o Opus nas 25 dele (as 3 falhas foram infraestrutura, não código). 53 de 53 tentativas elegíveis corretas entre os dois. A tarefa, feita pra ser difícil, foi fácil demais pra separar os modelos de fronteira, e muito menos as linguagens: com todo mundo passando em tudo, não existe ranking de acerto pra publicar.

A diferença apareceu nos modelos mais fracos, e na direção que a minha hipótese previa: teste fornecido ajudou. Kimi e GLM, com a suíte pública em mãos (T1), entregaram 24 artefatos corretos contra 16 sem ela (T0). O Kimi, em particular, passou nas 14 tentativas com testes fornecidos. É descritivo, não causal, mas é o sinal mais limpo do benchmark inteiro: o que mudou o resultado não foi a linguagem, foi a presença de teste.

Por linguagem, dentro de cada modelo, o acerto ficou espalhado entre 1/4 e 4/4 sem padrão que se repita entre modelos. As três falhas semânticas que eu auditei a fundo são instrutivas: um C# do GLM e um Python do Kimi perderam a mesma história por violar a mesma invariante entre operações, e um Haskell do Kimi errou um caminho de checkpoint. Nenhuma falha era erro de tipo.

Onde o modelo errou, errou invariante de domínio: o tipo de erro que compilador nenhum pega, em linguagem tipada ou dinâmica. O mito de que tipagem forte poupa o trabalho de entender o problema morre aí.

Resultado 2: linguagem concisa não economizou token

Aqui vai a tabela que mata a primeira superstição. Medianas por linguagem no Sol, quatro tentativas válidas por célula:

LinguagemTempo medianoTokens medianosLinhas finais do código
TypeScript53 s147,5 mil~280
Python77 s138,2 mil~300
C#61 s171,6 mil~260
Kotlin65 s170,6 mil~210
Haskell75 s184,7 mil~265
Ruby82 s185,6 mil~380
Rust80 s188,1 mil~650

Ruby, o mais conciso em linhas (pouco mais da metade do Rust), custou praticamente os mesmos tokens do Rust no Sol, e no Opus foi o mais caro de todos, uns 205 mil tokens de mediana contra 94 mil do C#. Os mais baratos foram TypeScript e Python no Sol, C#, Haskell e Kotlin no Opus. Se “menos linhas de código fonte = menos tokens de LLM” fosse lei, Ruby tinha que liderar de longe. Ficou no fim.

E a segunda superstição? Auditei os rastros procurando os ciclos de reparo guiados por compilador que a tipagem forte supostamente evita. Resultado: zero ciclos de reparo de erro de compilação nas tentativas auditadas do Opus, em Ruby, Rust e Haskell. O Rust não precisou de correção nenhuma vinda do compilador; o Haskell teve uma limpeza de warning; o Ruby gastou seus tokens extras em ajuste voluntário de performance. O modelo de fronteira quase sempre escreve código que compila de primeira, e o esforço real vai pra verificação, não pra apaziguar o compilador.

Um contraponto que fecha o caixão da tese “barato é bom”: as células de menor consumo de token do GLM foram as falhas. TypeScript T0 por 50 mil tokens parece eficiente até você ver que são dois starters inalterados porque o modelo estourou o limite de saída. Token baixo pode significar desistência precoce.

Resultado 3: sob carga, todo mundo empatou na fila

Aí vem a pergunta do DHH: Rust “ganha” de Elixir e Ruby quando o assunto é throughput. Montei a trilha separada pra isso: quatro workers de fila contra um RabbitMQ real, escritos com ajuda de agente, cada um com seu idioma natural:

  • Elixir com OTP e Broadway
  • Rust com Tokio e Lapin
  • Rails com Bunny
  • Ruby puro com Bunny (controle)

Um gerador publicava 10 mil mensagens de 4 KB no RabbitMQ, a 200 por segundo, e cada worker consumia o mais rápido que conseguia, batendo os efeitos num coletor HTTP/SQLite com atraso nominal de 50 ms, limitado a 2 CPUs e 1 GB de RAM. Dois perfis de concorrência:

StackHandlersEfeitos/sCorretas/10.000Pico de memóriaDrenou?
Elixir894,810.000135 MiBsim
Rust894,610.00016 MiBsim
Rails892,710.00079 MiBsim
Ruby892,510.00038 MiBsim
Elixir6484,87.915161 MiBnão
Rust6488,28.15123 MiBnão
Rails6488,58.10887 MiBnão
Ruby6488,08.08160 MiBnão

No perfil de 8 handlers, as quatro stacks entregaram a mesma vazão, 93 a 95 efeitos por segundo, e drenaram a fila inteira. O gargalo aparente era o coletor compartilhado (um lock em Python, um escritor SQLite), não as linguagens. No perfil de 64 handlers, todas deixaram backlog e estouraram o timeout do cliente em massa, então marquei essas células como não qualificadas: o protocolo forte, com coletor de group-commit e escada de 50 a 800 msg/s, está desenhado e não executado.

A única diferença que sobrou de pé nos números foi memória: Rust segurou o pico em 16 MiB onde a BEAM gastou 135. É uma vantagem real do Rust em densidade, com a ressalva de que pico de cgroup não é footprint de linguagem. Em vazão, que era a alegação da thread, empate técnico.

E o empate merece a leitura honesta: ele pode significar só que a minha pressão foi pouca. Com o coletor compartilhado segurando tudo em torno de 95 efeitos por segundo, qualquer stack capaz de mais que isso aparece como “igual”. O que esse teste mostra é onde estava o gargalo, não onde cada stack quebra. Pra achar o teto de cada uma eu precisaria do protocolo forte, com coletor de group-commit e escada de carga, que está desenhado e não executado. Até lá, “todas as stacks performam igual” é a conclusão errada; “todas ficaram acima do que o meu teste conseguiu medir” é a certa.

E a pergunta que interessava mais, “o agente trabalha menos pra entregar um consumer de fila confiável em Elixir do que em Rust?”, ficou sem resposta de propósito: essas quatro implementações foram escritas pra validar o ambiente, não como tentativa controlada de codificação. Comparar o esforço de IA entre Elixir e Rust exigiria a mesma matriz do ledger rodando na tarefa de fila. Está na lista.

Afinal, a linguagem importa?

Com os dados na mesa, dá pra responder as três perguntas da thread:

Existe linguagem melhor pra programar com LLM? Não apareceu nenhuma. Nos modelos de fronteira, acerto e esforço variaram menos entre linguagens do que entre modelos. Onde houve diferença de acerto, ela seguiu a presença de testes fornecidos, não a sintaxe. “Nenhum ranking geral de linguagem se estabelece” é a frase oficial do relatório, e é a frase correta.

As teses de economia de token se sustentam? Nenhuma das duas. Concisão não economizou: Ruby custou tanto quanto Rust, ou mais. Tipagem forte não poupou reparo: não havia reparo de tipo pra poupar. O esforço dos modelos foi pra verificação e entendimento do contrato, e isso não tem linguagem.

O DHH estava certo? No que ele mediu, ele mediu o que um agente entrega de graça em cada stack, sem ajuste, e chamou isso de veredito sobre linguagem. No meu teste, com carga real e medição honesta, as quatro stacks empataram em vazão no regime estável, e só a memória diferenciou o Rust. Se a thread dele prova alguma coisa, prova que conversão por agente mede o modelo, não a linguagem.

Minhas apostas da introdução saíram de pé: teste e disciplina são a alavanca, o compilador é coadjuvante, e a ferramenta certa depende do problema. Web app novo continua sendo Rails na minha mão, fila pesada continua sendo território Erlang, sistema baixo nível continua sendo Rust, e o LLM escreve qualquer uma delas com a mesma competência. Quem passou a semana brigando pela linguagem favorita no X brigou por nada.

E a minha prática diária conta a mesma história. Eu tenho viés pessoal pra Rust e mesmo assim já publiquei mais de 30 projetos open source em todo tipo de stack: o ai-memory é Rust, o FrankMD é Ruby on Rails, o ai-memory-web e o ai-jail-web são Node.js. Em nenhum deles eu senti diferença relevante que viesse da stack: os ganhos foram parecidos, os erros também, e os erros eram sempre compreensíveis. Cada erro virou melhoria de invariante, de regra, de skill. Nunca virou troca de linguagem.

Então esquece “economia de token” e “superstição de tipagem”. Faz engenharia de software de verdade: escolhe a ferramenta certa pro teu problema específico, aplica técnica de engenharia correta, e qualquer agente razoável vai performar mais ou menos igual. E para de gastar tempo discutindo rage bait de guerra de linguagem na internet. Seja engenheiro de software de verdade e faça engenharia de verdade. É só isso.

Os dados por tentativa, a metodologia completa e os limites de evidência estão no repositório. Se você rodar a matriz com outra tarefa e achar um ranking que eu não achei, me mostra.