Cargo e crates em Rust
Na página Começando com Rust você usou cargo new e cargo run. Aquilo foi só o começo. O Cargo é a ferramenta de build do Rust e também o seu gerenciador de pacotes. Ele compila seu código, roda seus testes, formata seus arquivos e baixa bibliotecas que outras pessoas escreveram.
Essas bibliotecas se chamam crates. Pense nos crates como apps em uma loja de aplicativos: alguém já resolveu um problema (ler JSON, fazer requisições web, interpretar datas), e você adiciona essa solução ao seu projeto com um único comando. Esta página mostra como isso funciona e como escolher um bom crate.
Crates e pacotes
Seção intitulada “Crates e pacotes”Duas palavras aparecem o tempo todo:
- Um crate é uma unidade de código Rust que o compilador constrói. Ele pode ser um binário (um programa que você roda, começando em
src/main.rs) ou uma biblioteca (código para outros programas usarem, começando emsrc/lib.rs). - Um pacote (package) é o que o
cargo newcria: uma pasta com um arquivoCargo.tomle um ou mais crates dentro.
No dia a dia, o pessoal fala “crate” para as duas coisas. Quando alguém diz “use o crate serde”, quer dizer “adicione o pacote serde, do crates.io, ao seu projeto”.
Por dentro do Cargo.toml
Seção intitulada “Por dentro do Cargo.toml”Todo pacote tem um arquivo Cargo.toml. Ele é escrito em TOML, um formato simples de configuração. Veja o que o cargo new hello_rust cria para você:
[package]name = "hello_rust"version = "0.1.0"edition = "2024"
[dependencies][package]descreve o seu projeto.nameeversionsão o que outras pessoas veriam se você o publicasse.editionescolhe qual edição da linguagem Rust usar. As edições (2018, 2021, 2024) permitem que o Rust evolua sem quebrar código antigo. É só manter o que ocargo newescreveu.[dependencies]lista os crates que o seu código usa. Começa vazia.
Conforme o projeto cresce, outras seções podem aparecer:
[dependencies]serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"
[dev-dependencies]# usado só pelos testes e exemplos, não pelo programa de verdadepretty_assertions = "1.4"
[profile.release]# configurações para `cargo build --release`opt-level = 3Adicionando uma dependência com cargo add
Seção intitulada “Adicionando uma dependência com cargo add”Você pode editar [dependencies] na mão, mas o jeito mais fácil é o cargo add. Ele encontra a versão mais recente no crates.io e escreve a linha para você:
cargo add serde_jsonAlguns crates têm partes opcionais chamadas features. Elas vêm desligadas por padrão, para manter o build enxuto. Você as liga com --features:
cargo add serde --features deriveDepois desses dois comandos, o seu Cargo.toml vai ter algo assim:
[dependencies]serde = { version = "1.0.228", features = ["derive"] }serde_json = "1.0.145"Agora é só usar o crate no seu código. O próximo cargo run baixa o crate, compila uma vez e o junta ao seu programa:
use serde_json::json;
fn main() { let crab = json!({ "legs": 10, "name": "Ferris" });
println!("{}", crab); println!("Nome: {}", crab["name"]);}Saída:
{"legs":10,"name":"Ferris"}Nome: "Ferris"As aspas em volta de "Ferris" aparecem porque crab["name"] ainda é um valor JSON, e não uma string comum do Rust.
O Rust Playground já vem com os crates mais populares, então esse exemplo roda lá sem cargo add. No seu computador, você precisa rodar cargo add serde_json antes. Você vai aprender muito mais sobre esse crate na página JSON com Serde.
Para remover um crate de que você não precisa mais, rode cargo remove serde_json.
Requisitos de versão e o Cargo.lock
Seção intitulada “Requisitos de versão e o Cargo.lock”Quando você escreve serde_json = "1.0", não está pedindo exatamente a versão 1.0. Está dizendo “qualquer versão compatível com a 1.0”. O Cargo segue o versionamento semântico (SemVer): uma versão tem o formato MAJOR.MINOR.PATCH, e só uma mudança no número major pode quebrar o seu código.
| Você escreve | O Cargo pode usar | Por quê |
|---|---|---|
"1.0" (o mesmo que "^1.0") |
>=1.0.0 e <2.0.0 |
todas as versões 1.x devem ser compatíveis |
"1.4.2" |
>=1.4.2 e <2.0.0 |
no mínimo 1.4.2, mas ainda 1.x |
"0.4" |
>=0.4.0 e <0.5.0 |
antes da 1.0, é o número minor que pode quebrar |
"~1.4" |
>=1.4.0 e <1.5.0 |
só atualizações de patch |
"=1.4.2" |
exatamente 1.4.2 |
raramente necessário |
Então quem decide a versão exata? Esse é o papel do Cargo.lock. No primeiro build, o Cargo escolhe as versões mais novas que atendem aos seus requisitos e as anota no Cargo.lock. Todos os builds seguintes usam essas mesmas versões exatas, no seu computador e no de todo mundo.
- Não edite o
Cargo.lockna mão. Quem cuida dele é o Cargo. - Faça commit dele no git, principalmente em programas. Assim o seu time e o seu CI constroem exatamente o que você construiu.
cargo updateatualiza para as versões mais novas que ainda atendem aoCargo.tomle reescreve oCargo.lock. Ele nunca pula para uma nova versão major; para isso, você mesmo altera oCargo.toml.
Encontrando um bom crate
Seção intitulada “Encontrando um bom crate”Há três sites que você vai usar o tempo todo:
- crates.io é o registro oficial. Todo crate público fica lá. Pesquise uma palavra como “csv” ou “date”.
- docs.rs hospeda a documentação de todos os crates, gerada automaticamente. Por exemplo, docs.rs/serde_json.
- lib.rs é um índice não oficial, mais rápido, que agrupa os crates por categoria. Ótimo para explorar.
Muitos crates resolvem o mesmo problema. Como escolher? Antes de adicionar um, tire um minutinho para conferir:
- Downloads. Crates populares têm milhões de downloads. Isso significa que muita gente já encontrou e corrigiu os bugs antes de você.
- Atualizações recentes. Veja a data da última versão. Um crate que não muda há anos pode estar ótimo (alguns simplesmente estão “prontos”), mas confira se o repositório não está cheio de issues sem resposta.
- Documentação. Abra a página dele no docs.rs. Bons crates têm uma página inicial clara, com um exemplo que você pode copiar.
- Versão 1.0 ou maior. Não é obrigatório, mas mostra que o autor promete uma API estável.
- Poucas dependências. Cada crate que você adiciona traz as próprias dependências. Quanto menor, mais fácil de confiar.
Na dúvida, pesquise “rust” junto com o seu problema e veja qual crate a comunidade recomenda mais vezes.
Comandos essenciais do Cargo
Seção intitulada “Comandos essenciais do Cargo”Você não precisa decorar nada disso. Deixe esta tabela por perto e você vai aprender usando:
| Comando | O que faz |
|---|---|
cargo new my_app |
Cria um novo projeto binário (use --lib para uma biblioteca) |
cargo build |
Compila em target/debug/ |
cargo run |
Compila e roda (passe argumentos depois de --: cargo run -- input.txt) |
cargo check |
Procura erros sem gerar o programa. Bem mais rápido que o build |
cargo test |
Roda os seus testes (veja Testes em Rust) |
cargo fmt |
Formata todo o seu código no estilo padrão |
cargo clippy |
Roda o linter, que sugere um código mais simples e idiomático |
cargo doc --open |
Gera a documentação HTML do seu projeto e de todas as dependências, e abre no navegador |
cargo update |
Atualiza o Cargo.lock para as versões compatíveis mais novas |
cargo build --release |
Compila um programa otimizado em target/release/ |
Um bom hábito: rode cargo check com frequência enquanto escreve, e cargo fmt e cargo clippy antes de fazer commit. O cargo doc --open também é uma joia escondida: ele mostra a documentação das versões exatas dos crates que você usa, e funciona offline.
Build de debug vs. build de release
Seção intitulada “Build de debug vs. build de release”Por padrão, o Cargo faz um build de debug. Ele compila rápido e mantém verificações extras de segurança, como dar panic quando um inteiro estoura (overflow). É perfeito enquanto você está desenvolvendo.
Um build de release (cargo build --release ou cargo run --release) demora mais para compilar, mas roda muito mais rápido, muitas vezes 10 vezes ou mais. Use quando for entregar o programa ou medir a velocidade dele.
O seu código pode até descobrir que tipo de build ele é:
fn main() { if cfg!(debug_assertions) { println!("Build de debug: verificações extras ligadas"); } else { println!("Build de release: otimizado para velocidade"); }}Saída:
Build de debug: verificações extras ligadasNo Playground, você pode trocar de “Debug” para “Release” no menu e rodar de novo para ver a outra linha.
Crates binário e biblioteca juntos
Seção intitulada “Crates binário e biblioteca juntos”Um pacote pode ter uma biblioteca e um binário ao mesmo tempo. Essa organização é muito comum: a lógica de verdade fica em src/lib.rs, e o src/main.rs continua pequeno, só chamando essa lógica:
greeter/├── Cargo.toml└── src/ ├── lib.rs # o crate de biblioteca: lógica reutilizável └── main.rs # o crate binário: o programa que você rodapub fn greet(name: &str) -> String { format!("Olá, {name}!")}fn main() { // a biblioteca tem o mesmo nome do pacote println!("{}", greeter::greet("Ferris"));}Por que separar assim? O código em lib.rs é fácil de testar, e outros programas (ou um segundo binário) podem reutilizá-lo. O Playground só tem um arquivo, mas dá para ter a mesma ideia com um pub mod, como na página de Módulos:
mod greeter { pub fn greet(name: &str) -> String { format!("Olá, {name}!") }}
fn main() { println!("{}", greeter::greet("Ferris"));}Saída:
Olá, Ferris!Uma olhada rápida em workspaces
Seção intitulada “Uma olhada rápida em workspaces”Quando um projeto cresce e vira vários pacotes que andam juntos (por exemplo, um app de linha de comando, um servidor web e uma biblioteca compartilhada), você pode colocá-los em um único workspace. Eles compartilham um só Cargo.lock e uma só pasta target/, então tudo é compilado junto e usa as mesmas versões das dependências.
O Cargo.toml principal lista os membros:
[workspace]members = ["app", "core"]resolver = "3"my_project/├── Cargo.toml # o arquivo de workspace acima├── Cargo.lock # compartilhado por todos os membros├── app/ # um pacote binário└── core/ # um pacote de bibliotecaAí, cargo build na pasta principal compila tudo, e cargo run -p app roda um dos membros. Você não vai precisar disso nos seus primeiros projetos, mas é bom reconhecer quando encontrar no código de outras pessoas.
Erros comuns
Seção intitulada “Erros comuns”Usar um crate sem adicioná-lo. Se você escreve use serde_json::json; mas esqueceu do cargo add serde_json, o compilador não encontra o crate:
error[E0432]: unresolved import `serde_json` --> src/main.rs:1:5 |1 | use serde_json::json; | ^^^^^^^^^^ use of unresolved module or unlinked crate `serde_json` | = help: you might be missing a crate named `serde_json`O erro diz que o import serde_json não foi resolvido e sugere que talvez esteja faltando um crate com esse nome. A solução é rodar cargo add serde_json. Confira também a grafia: nomes de crates usam - ou _, e no código um - sempre vira _.
Esquecer uma feature. Se o #[derive(Serialize)] reclama que não encontra a macro derive, provavelmente você adicionou o serde sem --features derive. Rode cargo add serde --features derive de novo; ele atualiza a linha que já existe.
Medir velocidade com um build de debug. Se o seu programa parece lento, tente cargo run --release antes de mudar qualquer código.
Prática
Seção intitulada “Prática”1. Use um crate no Playground
Seção intitulada “1. Use um crate no Playground”Use a macro json! do serde_json para descrever um crate com três chaves: downloads (um número), name e version. Imprima o valor inteiro e depois só a versão.
Dica: copie o formato do exemplo crab em Adicionando uma dependência com cargo add. No seu computador, rode cargo add serde_json primeiro; o Playground já tem esse crate.
Ver solução
use serde_json::json;
fn main() { let krate = json!({ "downloads": 500, "name": "serde_json", "version": "1.0" });
println!("{}", krate); println!("Versão: {}", krate["version"]);}Saída:
{"downloads":500,"name":"serde_json","version":"1.0"}Versão: "1.0"A variável se chama krate porque crate é uma palavra reservada em Rust. Essa grafia é uma piada comum em código Rust. Como antes, a versão sai entre aspas porque ainda é um valor JSON; a página JSON com Serde mostra como tirar o texto puro de dentro dele.
2. Mensagens só em builds de debug
Seção intitulada “2. Mensagens só em builds de debug”Escreva uma função debug_log(msg: &str) que imprime [debug] seguido da mensagem, mas só em um build de debug. Use essa função no main em volta de um cálculo pequeno e imprima o resultado normalmente.
Dica: coloque o println! dentro de if cfg!(debug_assertions) { ... }, como em Build de debug vs. build de release.
Ver solução
fn debug_log(msg: &str) { if cfg!(debug_assertions) { println!("[debug] {msg}"); }}
fn main() { debug_log("começando o cálculo"); let mut total = 0; for n in 1..=10 { total += n; } debug_log("cálculo concluído");
println!("A soma de 1 a 10 é {total}");}Saída:
[debug] começando o cálculo[debug] cálculo concluídoA soma de 1 a 10 é 55Mude o Playground para “Release” (ou use cargo run --release no seu computador) e rode de novo: só a última linha aparece. Em projetos de verdade, a página de Logging mostra crates que fazem isso direito, com níveis como debug e info.
- O Cargo compila, roda, testa e formata o seu código, e baixa crates do crates.io.
- O
Cargo.tomlé o que você pede; oCargo.lockregistra as versões exatas que você recebeu. Faça commit dos dois. cargo add nome --features xadiciona uma dependência."1.0"quer dizer “qualquer versão 1.x compatível”.- Avalie um crate pelos downloads, pelas atualizações recentes e por uma boa documentação no docs.rs.
- Use
cargo checkenquanto escreve,cargo fmtecargo clippyantes do commit, e--releasequando precisar de velocidade. - Coloque a lógica reutilizável em
src/lib.rse mantenha osrc/main.rspequeno.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. No Cargo.toml, o que significa serde_json = "1.0"?
2. Qual arquivo registra as versões exatas de cada dependência usada no seu build?
3. Você quer procurar erros no seu código o mais rápido possível, sem gerar um programa. Qual comando é o mais indicado?
4. Como você adiciona o serde com a feature derive ligada?
Pontuação: 0 / 4
Para saber mais
Seção intitulada “Para saber mais”- The Cargo Book (em inglês): o guia e a referência oficiais de tudo o que o Cargo faz.
- Specifying Dependencies — The Cargo Book (em inglês): todas as formas de escrever um requisito de versão, além de dependências via git e por caminho local.
cargo add— The Cargo Book (em inglês): todas as opções docargo add, como--dev,--featurese--no-default-features.- More about Cargo and Crates.io — The Rust Book (em inglês): perfis de release, como publicar o seu próprio crate e como instalar ferramentas com
cargo install. - Cargo Workspaces — The Rust Book (em inglês): um workspace passo a passo, com um binário e duas bibliotecas.