---
title: "cargo-llvm-lines: Monomorfização Rust | Rust Brasil"
url: "https://rustlang.com.br/blog/cargo-llvm-lines-monomorfizacao-rust-2026/"
markdown_url: "https://rustlang.com.br/blog/cargo-llvm-lines-monomorfizacao-rust-2026.MD"
description: "Use cargo-llvm-lines para encontrar genéricos que geram muito LLVM IR em Rust. Guia com instalação, leitura do relatório, workspaces, LTO e otimização."
date: "2026-08-07"
author: "Equipe Rust Brasil"
---

# cargo-llvm-lines: Monomorfização Rust | Rust Brasil

Use cargo-llvm-lines para encontrar genéricos que geram muito LLVM IR em Rust. Guia com instalação, leitura do relatório, workspaces, LTO e otimização.


**O `cargo-llvm-lines` é a ferramenta certa quando você suspeita que genéricos, iteradores ou macros estão fazendo um projeto Rust gerar código demais durante a compilação.** Instale com `cargo install cargo-llvm-lines --locked` e execute `cargo llvm-lines`. O relatório ordena funções pelo total de linhas de LLVM IR e mostra quantas instanciações — ou “cópias” — foram geradas para cada uma.

A leitura mais útil é: uma função com muitas **Lines** e muitas **Copies** merece investigação. Isso não significa que ela esteja errada. A monomorfização é justamente o mecanismo que permite a abstrações genéricas entregarem código especializado e sem dispatch dinâmico. O problema aparece quando a especialização se multiplica sem trazer benefício proporcional para o produto.

Este guia explica como usar `cargo-llvm-lines`, interpretar o relatório, analisar workspaces, distinguir sinal de ruído e testar alternativas sem trocar performance por uma redução apenas estética na quantidade de IR.

## O que o cargo-llvm-lines mede

O compilador Rust transforma o programa por várias etapas antes de produzir o executável. De forma simplificada:

1. o código Rust é analisado e verificado;
2. tipos, traits e empréstimos são resolvidos;
3. funções genéricas são monomorfizadas para tipos concretos;
4. o compilador produz uma representação intermediária do LLVM;
5. o LLVM otimiza essa representação e gera código de máquina;
6. o linker monta o artefato final.

O `cargo-llvm-lines` atua próximo da quarta etapa. Ele compila o target emitindo LLVM IR e conta as linhas associadas às funções encontradas. Segundo a [documentação oficial do projeto](https://github.com/dtolnay/cargo-llvm-lines), a ferramenta foi criada para mostrar quais partes do código oferecem maior alavancagem na melhoria de métricas de compilação.

Ela produz três informações principais:

| Coluna | O que representa | Como usar |
|---|---|---|
| `Lines` | Total de linhas de LLVM IR de todas as instanciações | Localizar grandes geradores de código |
| `Copies` | Quantidade de instanciações da função | Identificar multiplicação por tipos genéricos |
| `Function name` | Nome da função ou implementação | Descobrir qual API investigar |

A contagem não é um cronômetro e também não é o tamanho final do binário. É um **proxy de geração de código**. Mais IR pode contribuir para build mais lento, mais memória usada pelo compilador e executável maior, mas cada hipótese precisa ser confirmada com outra medição.

## O que é monomorfização em Rust

Considere uma função genérica simples:

```rust
fn duplicar<T: Clone>(valor: T) -> (T, T) {
    (valor.clone(), valor)
}

fn main() {
    let _numeros = duplicar(vec![1_u64, 2, 3]);
    let _texto = duplicar(String::from("Rust Brasil"));
}
```

O compilador pode gerar uma versão especializada de `duplicar` para `Vec<u64>` e outra para `String`. Essa estratégia evita uma chamada indireta em runtime e abre espaço para inlining e outras otimizações.

Agora imagine uma função genérica grande chamada com dezenas de tipos, em vários módulos e crates. O corpo especializado pode se repetir muitas vezes. O resultado potencial é:

- mais trabalho de compilação;
- mais memória usada por `rustc` e LLVM;
- mais código para o otimizador processar;
- crescimento do executável;
- builds incrementais menos previsíveis em certos cenários.

Nada disso torna genéricos ruins. Traits, iteradores e abstrações de custo zero são parte central de Rust. O objetivo do `cargo-llvm-lines` é encontrar os poucos pontos em que a expansão ficou desproporcional.

Para uma base conceitual, revise nosso guia de [traits e generics em Rust](/tutoriais/traits-generics/) e o artigo sobre [iteradores Rust](/artigos/iteradores-rust/).

## Instalação e primeiro relatório

Instale a ferramenta pelo Cargo:

```bash
cargo install cargo-llvm-lines --locked
cargo llvm-lines --version
```

Na raiz de um projeto:

```bash
cargo llvm-lines
```

Para analisar o perfil de release:

```bash
cargo llvm-lines --release
```

A saída começa com um total e segue com funções ordenadas por linhas. Um exemplo resumido seria:

```text
 Lines            Copies          Function name
 -----            ------          -------------
 18240              420           (TOTAL)
  2100 (11.5%)       28           minha_crate::processar
  1450 ( 7.9%)       41           core::option::Option<T>::map
   980 ( 5.3%)        7           minha_crate::serializar
```

Os números acima são ilustrativos. A primeira pergunta é: **as linhas estão concentradas em uma função com muitas cópias ou em uma função grande com poucas cópias?**

- Muitas linhas e muitas cópias sugerem expansão por monomorfização;
- muitas linhas e uma cópia sugerem uma função grande, macro expandida ou código complexo;
- muitas cópias e poucas linhas por cópia podem ser aceitáveis;
- uma função da biblioteca padrão no topo pode ter sido instanciada pelo seu código, não ser um problema da biblioteca.

## Ordene por cópias e filtre funções

Por padrão, o relatório é ordenado por linhas. Para destacar quantidade de instanciações:

```bash
cargo llvm-lines --sort copies
```

Para ordenar pelo nome:

```bash
cargo llvm-lines --sort name
```

Também é possível filtrar nomes com expressão regular:

```bash
cargo llvm-lines --filter 'minha_crate::'
cargo llvm-lines --filter 'serializ|parse|format'
```

O filtro é útil quando uma dependência ou a biblioteca padrão domina a tabela e você quer examinar uma área específica. Ainda assim, não esconda o relatório completo cedo demais: uma API chamada no seu código pode aparecer com um caminho de função que você não esperava.

Uma sequência prática é:

```bash
cargo llvm-lines > /tmp/llvm-lines-total.txt
cargo llvm-lines --sort copies > /tmp/llvm-lines-copies.txt
cargo llvm-lines --filter 'nome_da_crate::' > /tmp/llvm-lines-local.txt
```

Assim você mantém a visão geral, a lista de multiplicação e o recorte do código próprio.

## Como analisar workspaces e múltiplos targets

Em [Cargo workspaces](/blog/cargo-workspaces-monorepos-rust-2026/), selecione o pacote com `-p` ou `--package`:

```bash
cargo llvm-lines -p minha-biblioteca
cargo llvm-lines -p minha-api --release
```

Quando o pacote tem mais de um target, seja explícito:

```bash
cargo llvm-lines -p minha-api --bin servidor --release
cargo llvm-lines -p minha-biblioteca --lib --release
cargo llvm-lines -p ferramentas --example importador
```

A ferramenta aceita opções comuns de seleção do Cargo, incluindo `--features`, `--all-features`, `--no-default-features`, `--target` e `--manifest-path`. Isso permite comparar configurações reais:

```bash
cargo llvm-lines --release --no-default-features
cargo llvm-lines --release --features json,postgres
cargo llvm-lines --release --all-features
```

Essa comparação costuma revelar que uma feature opcional introduz várias instanciações ou que o perfil “com tudo” não representa o binário realmente entregue.

### A limitação mais importante em projetos com várias crates

Por padrão, `cargo llvm-lines` mostra a contribuição da crate raiz analisada; dependências não aparecem como um inventário completo. Genéricos também são monomorfizados na crate que os usa, e não necessariamente naquela que os define.

Para inspecionar uma crate intermediária, use:

```bash
cargo llvm-lines -p nome-da-dependencia
```

Para obter uma visão mais ampla com a geração de código concentrada na crate raiz, a documentação sugere habilitar LTO fat temporariamente:

```bash
CARGO_PROFILE_RELEASE_LTO=fat cargo llvm-lines --release
```

Use esse comando como diagnóstico separado. LTO muda o pipeline e pode tornar a execução bem mais lenta; não compare o resultado com uma baseline sem LTO como se apenas uma função tivesse mudado.

## Como criar uma baseline reproduzível

Um relatório isolado é menos valioso do que uma comparação antes/depois. Registre ambiente, commit e configuração:

```bash
rustc --version --verbose
cargo --version
git rev-parse HEAD
cargo llvm-lines --release > /tmp/llvm-antes.txt
```

Faça uma alteração por vez e execute:

```bash
cargo llvm-lines --release > /tmp/llvm-depois.txt
diff -u /tmp/llvm-antes.txt /tmp/llvm-depois.txt
```

Mantenha constantes:

- versão do Rust e do LLVM;
- `Cargo.lock`;
- target de compilação;
- profile;
- features;
- variáveis em `RUSTFLAGS`;
- pacote e target selecionados.

Uma atualização da toolchain pode mudar o IR mesmo sem alteração no código-fonte. Por isso, a baseline deve registrar `rustc --version --verbose`, e não apenas “stable”.

## Padrões que merecem investigação

### Função genérica grande usada com muitos tipos

```rust
fn transformar<T, U>(entrada: T) -> U
where
    T: Validar + Normalizar + Serializar,
    U: From<T> + Persistir,
{
    // muitas etapas e branches
    todo!()
}
```

Se o corpo é grande e a função recebe dezenas de combinações de `T` e `U`, cada especialização pode carregar boa parte da lógica. Uma alternativa é separar uma camada genérica pequena de um núcleo não genérico.

### Cadeias longas de iteradores em pontos muito repetidos

Iteradores normalmente geram código excelente. Porém, closures e adaptadores combinados em muitos tipos podem aparecer com várias instanciações. Não reescreva tudo como `for` por princípio; compare legibilidade, benchmark e relatório.

### Serialização genérica espalhada pela aplicação

APIs como `fn responder<T: Serialize>(valor: T)` são convenientes. Se cada endpoint instancia uma cadeia grande com um tipo diferente, pode valer concentrar etapas comuns depois que a serialização já virou `Vec<u8>` ou outro formato concreto.

### Logging e formatação em funções genéricas

Macros de logging e formatação podem expandir bastante dentro de uma função que será monomorfizada muitas vezes. Mover mensagens invariantes ou etapas não genéricas para uma função auxiliar pode reduzir repetição sem alterar a API pública.

### Enums, builders e macros geradas

Código gerado por derive ou macros pode criar implementações extensas. O relatório mostra o efeito compilado, embora a origem esteja em um `#[derive(...)]` aparentemente pequeno. Confirme com expansão de macro e documentação da crate antes de remover recursos.

## Estratégias de redução sem destruir a API

### Extraia um núcleo não genérico

Em vez de manter toda a lógica no caminho genérico:

```rust
fn processar<T: AsRef<[u8]>>(entrada: T) -> Resultado {
    let bytes = entrada.as_ref();
    validar(bytes);
    decodificar(bytes)
}
```

Separe a conversão da implementação principal:

```rust
fn processar<T: AsRef<[u8]>>(entrada: T) -> Resultado {
    processar_bytes(entrada.as_ref())
}

fn processar_bytes(bytes: &[u8]) -> Resultado {
    validar(bytes);
    decodificar(bytes)
}
```

A função genérica fica pequena, enquanto o trabalho maior vive em uma única função concreta. O compilador ainda pode fazer inlining; valide o resultado real.

### Use trait objects quando a troca fizer sentido

Trocar `T: Trait` por `&dyn Trait` reduz algumas especializações, mas introduz dispatch dinâmico e pode limitar otimizações. É adequado quando:

- a operação não está em um hot path;
- diferentes implementações já precisam coexistir em runtime;
- o custo da chamada indireta é irrelevante diante do trabalho realizado;
- a redução foi confirmada e a API continua clara.

Não use trait objects apenas para diminuir `Copies` em uma tabela.

### Faça type erasure depois da fronteira de performance

Outra estratégia é manter a etapa crítica genérica e converter o resultado para um tipo concreto antes de um pipeline grande. Assim, você preserva especialização onde ela traz valor e evita replicá-la no restante da aplicação.

### Reduza combinações de tipos acidentais

Às vezes o mesmo conceito circula como `String`, `&str`, `Cow<'_, str>`, `Arc<str>` e wrappers diferentes sem necessidade. Padronizar a fronteira interna pode reduzir instanciações e também simplificar a arquitetura.

## Confirme com tempo de build e tamanho do binário

Uma queda de 20% em linhas de IR não garante build 20% mais rápido. Depois da alteração, meça pelo menos:

```bash
cargo clean
/usr/bin/time -v cargo build --release --locked
```

Repita algumas vezes em ambiente controlado. Para uma visão complementar das etapas, use os recursos descritos no guia de [como reduzir o tempo de compilação Rust](/blog/rust-tempo-compilacao-otimizar-build-2026/).

Para verificar o artefato final:

```bash
cargo install cargo-bloat --locked
cargo bloat --release --crates
cargo bloat --release -n 20
```

Nosso guia de [cargo-bloat para reduzir binários Rust](/blog/cargo-bloat-reduzir-tamanho-binario-rust-2026/) explica essa análise em detalhes. A combinação correta é:

| Ferramenta | Pergunta principal |
|---|---|
| `cargo-llvm-lines` | Onde estão muitas linhas de IR e instanciações? |
| medição de build | A mudança reduziu tempo e memória de compilação? |
| `cargo-bloat` | A mudança reduziu código no executável? |
| benchmark/profiler | A mudança preservou performance em runtime? |

Use também [sccache para cache de compilação](/blog/sccache-rust-cache-compilacao-ci-2026/) e [mold para acelerar o link](/blog/mold-linker-rust-compilacao-rapida-2026/) quando o gargalo não for geração repetida de código.

## Erros comuns ao usar cargo-llvm-lines

### Tratar Lines como tempo

Linhas de IR são um indicador. O custo de otimização varia conforme a natureza do código, e partes diferentes podem exigir trabalhos diferentes do LLVM.

### Culpar a biblioteca padrão

Uma função de `core`, `alloc` ou outra crate pode aparecer porque seu código a instancia muitas vezes. Investigue o call site e os tipos usados antes de atribuir culpa à dependência.

### Comparar debug com release

Profiles diferentes alteram geração de código e otimização. Compare configurações equivalentes.

### Otimizar apenas o total

Reduzir o `(TOTAL)` e piorar a API, a segurança ou a performance é uma derrota. O total ajuda a localizar candidatos; não é uma meta de produto.

### Usar `dyn Trait` em hot paths sem benchmark

Menos monomorfização pode significar menos oportunidade de inlining e vetorização. Meça a carga real.

### Ignorar features e targets

O relatório de um binário Linux sem TLS não representa uma release Windows com todas as features. Analise o produto que você realmente distribui.

### Colocar um limite rígido na CI cedo demais

A contagem pode mudar com a toolchain. Primeiro salve relatórios e observe a variação normal. Se adotar um orçamento, fixe o Rust e compare deltas relevantes, não um número eterno.

## Um fluxo de investigação em 10 passos

1. confirme se o problema é build, memória do compilador ou tamanho do artefato;
2. fixe toolchain, `Cargo.lock`, target, profile e features;
3. rode `cargo llvm-lines` e salve o total;
4. rode novamente com `--sort copies`;
5. filtre a crate e as funções do domínio;
6. selecione um único candidato com muitas linhas e cópias;
7. extraia lógica não genérica ou reduza combinações acidentais de tipos;
8. compare o relatório antes/depois;
9. meça build limpo, build incremental, binário e benchmark;
10. mantenha a mudança apenas se o ganho justificar a complexidade.

Esse processo também é uma competência relevante para engenharia de compiladores, sistemas, embedded e plataformas internas. Quem sabe explicar monomorfização com dados demonstra mais maturidade do que quem apenas repete que Rust “compila devagar”. Veja as [vagas Rust](/vagas/) e as [empresas que usam Rust](/empresas/) para acompanhar oportunidades do ecossistema.

## cargo-llvm-lines, Rust e outras linguagens de sistemas

A tensão entre especialização e crescimento de código não é exclusiva de Rust. Linguagens e toolchains escolhem diferentes momentos para executar metaprogramação, gerar especializações ou compartilhar implementações. Para comparar outra abordagem moderna, o portal <a href="https://ziglang.com.br/artigos/comptime-zig-metaprogramacao/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'ziglang.com.br' })">Zig Brasil explica comptime e metaprogramação em Zig</a>.

A comparação útil não é “qual linguagem gera menos linhas”. É entender como a abstração escolhida afeta tempo de build, tamanho, performance, depuração e manutenção no contexto do produto.

## Perguntas frequentes

### O que é cargo-llvm-lines?

É uma ferramenta que compila um target Rust emitindo LLVM IR e conta linhas e instanciações por função. Ela destaca genéricos e outros pontos que geram muito código intermediário.

### Como instalar e executar?

```bash
cargo install cargo-llvm-lines --locked
cargo llvm-lines
cargo llvm-lines --release
```

Em workspaces, use `-p nome-do-pacote`. Para targets específicos, combine com `--bin`, `--lib`, `--example`, `--test` ou `--bench`.

### O que significam Lines e Copies?

`Lines` é o total de linhas de LLVM IR somado entre as instanciações daquela função. `Copies` é a quantidade de instanciações; para genéricos, aproxima quantas combinações concretas de tipos foram geradas.

### A ferramenta mede tempo de compilação?

Não. Ela mede geração de IR. Use o resultado para criar uma hipótese e confirme com builds temporizados, consumo de memória e ferramentas como `cargo build --timings`.

### Qual é a diferença para cargo-bloat?

`cargo-llvm-lines` investiga geração de LLVM IR e monomorfização. `cargo-bloat` investiga bytes de código no executável compilado. Uma função pode gerar muito IR e ser bastante reduzida pelas otimizações; por isso as duas medidas não são equivalentes.

## Conclusão

O `cargo-llvm-lines` torna a monomorfização observável. Em vez de culpar genericamente traits, iteradores ou o compilador, você consegue apontar quais funções geram mais IR, quantas instanciações existem e onde uma mudança arquitetural pequena pode ter maior impacto.

O fluxo recomendado é: **gere uma baseline reproduzível, ordene por linhas e cópias, investigue um candidato, extraia o núcleo não genérico quando fizer sentido e confirme tudo com build, cargo-bloat e benchmark**. Não persiga a menor tabela possível. Persiga um projeto que compile dentro do orçamento e continue entregando segurança, performance e uma API sustentável.

Para continuar a investigação, leia [otimização de performance em Rust](/artigos/otimizacao-performance/), [ferramentas essenciais do Cargo](/artigos/cargo-ferramentas-essenciais/) e o guia de [cargo-zigbuild para cross-compilation](/blog/cargo-zigbuild-cross-compile-rust-2026/). Juntas, essas ferramentas ajudam a separar gargalo de compilação, gargalo de link, tamanho de artefato e performance de runtime — quatro problemas diferentes que pedem medições diferentes.
