Como migrar código legado para Rust aos poucos, função por função
Na QCon San Francisco, a staff engineer do Discord Lily Mara mostrou como trocar gargalos de Python por Rust via PyO3, sem reescrever a aplicação inteira.

Reescrever um sistema inteiro em Rust↳Rust7 conteúdosDesmistificando Rust: a linguagem segura e rápida que você precisa conhecerDev (Back & Front) · out 2024Como criar seu primeiro Programa em Rust com Solana PlaygroundDev (Back & Front) · mai 2026Rust no ranking: a segurança de memória perdeu a guerra corporativa?Marketing Tech · abr 2026Ver tudo em Dev (Back & Front) → é uma tentação recorrente para quem vê a linguagem entregar performance muito acima de Python, Ruby ou Node. Na apresentação Accelerating Performance by Incrementally Integrating Rust Into Existing Codebase, na QCon San Francisco, Lily Mara, staff engineer no time de Notifications Platform do Discord e autora do livro Refactoring to Rust, argumenta que esse impulso costuma dar errado, e propõe um caminho bem mais granular do que quebrar o monolito em microsserviços.
A proposta é o que ela chama de FFI refactoring: em vez de reescrever o serviço ou até o sistema, você troca a implementação de uma função específica por Rust e conecta as duas linguagens pela interface de funções em C (a C Foreign Function Interface). O argumento técnico é simples: praticamente todo sistema operacional e toda linguagem mainstream sabe chamar uma função C, porque existe uma montanha de código C rodando no mundo. Essa é a lingua franca que permite plugar Python em Rust de forma performática.
Por que o rewrite completo costuma falhar
Mara é direta sobre o problema da reescrita total, apoiada na própria experiência com Rust desde 2019 (e mais de uma década de uso somando projetos paralelos). Segundo ela, reescritas completas:
- Estouram prazos com frequência, por serem mais complexas do que se imaginava;
- Introduzem bugs novos, ou reintroduzem bugs antigos que o sistema legado já tinha corrigido;
- Ignoram o conhecimento histórico e institucional embutido no código velho. Nas palavras dela, código antigo não é ruim só por ser antigo: ele costuma ser complicado porque lida com um conjunto complicado de restrições do mundo real.
Outro ponto: quem reescreve mirando só performance tende a olhar apenas para a linguagem. E o custo por linha de CPU é só uma parte da conta. Mudanças de arquitetura, schema de banco, padrões de query, camadas de cache e as várias camadas de microsserviços costumam pesar tanto ou mais que a linguagem em si.
O que faz um bom candidato
A estratégia não vale para tudo, e o critério de Mara é agregado: olhe onde a aplicação gasta a maior parte do tempo. Isso pode ser uma operação cara que acontece de vez em quando, ou uma operação barata que acontece o tempo todo.
O exemplo que ela dá é revelador para quem mantém plataforma: o código de verificação de request que fica na frente de todos os handlers de API. Ele não é pesado por chamada, mas roda em toda requisição. Se 10% do tempo de execução dos servidores está nessa lógica interna, derrubar isso para 1% (ou meio por cento) não é uma redução trivial de custo numa organização grande. É exatamente o tipo de ganho de infraestrutura que a apresentação promete sem o overhead de criar um microsserviço.
Para decidir onde aplicar, Mara usa dois eixos: performance do Rust frente à linguagem atual, e suporte de ferramental para o FFI refactoring do lado do Rust.
| Linguagem | Ganho de performance com Rust | Ferramental de FFI no Rust |
|---|---|---|
| Python, Ruby, Node.js, Lua | Alto | Excelente (melhores candidatos) |
| Go | Rust costuma ser mais rápido | Fraco (runtime limita a FFI em C) |
| C, C++ | Semelhante (às vezes C é levemente mais rápido) | Bom, atrai por memory safety |
O detalhe sobre Go merece atenção de quem tem serviços na linguagem: o suporte à FFI em C é limitado pelo runtime, que faz muitas suposições sobre como ceder execução e precisa suspender parte disso ao chamar uma função C, com custo por chamada por causa da inflação do tamanho da stack. Já para C e C++, o atrativo não é velocidade, e sim as garantias de memory safety do Rust, motivo pelo qual projetos como Android e os kernels de Windows e Linux↳Linux34 conteúdosKali Linux em um Servidor VPS: como, quando e por que usar?DevSecOps · dez 2024Construindo um Windows Service ou Linux Daemon com Worker Service & .NET Core – Parte 2Dev (Back & Front) · jul 2020Criando uma WebApi utilizando .NET, Linux e VSCodeDev (Back & Front) · ago 2019Ver tudo em DevSecOps → vêm adotando a linguagem.
Os tradeoffs que você vai pagar
Não existe bala de prata, e Mara é honesta sobre o preço:
- Deploy mais complexo. Sai o modelo de subir código dinâmico e recarregar um serviço
systemd; entra código nativo custom no pipeline. - Ambiente de dev mais complexo. Ou você coloca o compilador Rust nas máquinas dos devs, ou envia binários nativos que precisam bater com o SO e a microarquitetura de cada máquina, ou mantém duas implementações em paralelo (a dinâmica antiga e a nova em Rust).
- Risco de bugs. Existe, embora menor do que num rewrite gigante, justamente por operar numa escala menor e mais restrita.
Como fica na prática: Python + PyO3
O exemplo concreto parte de uma aplicação Flask com um endpoint que faz cálculos estatísticos sobre uma lista de números vinda do corpo JSON (range, quartis, média e desvio padrão). A meta é mover o cálculo para Rust.
A peça central é o PyO3, o crate que faz a ponte entre Rust e Python (o nome brinca com a oxidação: Python trioxide, Python oxidado). O fluxo desenhado por Mara é: Flask recebe o request, desserializa o JSON, passa os valores para a função de cálculo em Rust, recebe o resultado de volta, serializa em JSON e devolve na resposta HTTP.
Do lado do Rust, cria-se um crate novo (rstats) com dependências de uma biblioteca de estatística (a std do Rust é enxuta, com primitivas de SO e sincronização) e do próprio PyO3, habilitando a feature extension-module, terminologia do Python para uma biblioteca C chamável. No Cargo.toml, é preciso dizer ao compilador para gerar uma biblioteca dinâmica C (cdylib), em vez do artefato Rust padrão, que só é chamável pela mesma versão do compilador no mesmo hardware:
[lib]
name = "rstats"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "...", features = ["extension-module"] }No código Rust, atributos do PyO3 fazem o trabalho pesado de geração de wrappers: #[pymodule] transforma o módulo Rust num módulo importável do Python, e #[pyfunction] gera uma função wrapper (por exemplo compute_stats) que, do lado Python, é chamada como qualquer função normal e por baixo aciona o código Rust. O nome do módulo precisa coincidir com o nome do crate, e será o nome importável do Python. Para os dados de retorno, cria-se uma struct que carrega as propriedades estatísticas de volta para o Python.
A compilação usa o Maturin, ferramenta dos próprios desenvolvedores do PyO3, que empacota o crate como módulo Python. Feito isso, o import rstats no código Flask funciona sem erro. Mara destaca o quão pouco código é preciso para ter um módulo Rust importável do Python, um ponto que reduz a barreira de entrada de quem nunca fez binding entre linguagens.
O que isso muda para quem constrói software aqui
Para o dev brasileiro que mantém um monolito Python, Ruby ou Node em produção, o recado é prático: dá para atacar gargalo por gargalo sem parar a esteira nem reescrever o sistema. A pergunta que orienta a decisão não é "qual linguagem é mais rápida", e sim "onde meu tempo de CPU está concentrado no agregado", e o alvo natural é aquela função quente que roda em toda requisição.
Vale medir antes de mexer: identifique o hotspot com profiling, confirme que o ganho justifica o custo extra de deploy e de ambiente de dev, e trate o código legado com respeito, ele carrega correções que um rewrite ingênuo joga fora. Referências úteis citadas na apresentação são o livro gratuito The Rust Programming Language, de Carol Nichols e Steve Klabnik, e o próprio Refactoring to Rust, de Mara. A gravação completa (49min47) está disponível no InfoQ.
Fonte: InfoQ
Este artigo foi escrito por Redação iMasters, um agente de inteligência artificial com revisão editorial humana. Publicado sob revisão editorial de Rafael Chinaglia - iMasters e validação técnica de Tiago Baeta. Saiba como produzimos no expediente.








Comentários
Ninguém comentou ainda. Começa a conversa?