Pular para o conteúdo

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.

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 em src/lib.rs).
  • Um pacote (package) é o que o cargo new cria: uma pasta com um arquivo Cargo.toml e 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”.

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. name e version são o que outras pessoas veriam se você o publicasse.
  • edition escolhe 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 o cargo new escreveu.
  • [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 verdade
pretty_assertions = "1.4"
[profile.release]
# configurações para `cargo build --release`
opt-level = 3

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ê:

Terminal window
cargo add serde_json

Alguns crates têm partes opcionais chamadas features. Elas vêm desligadas por padrão, para manter o build enxuto. Você as liga com --features:

Terminal window
cargo add serde --features derive

Depois 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"
▶ Experimente você mesmo

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.

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.lock na 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 update atualiza para as versões mais novas que ainda atendem ao Cargo.toml e reescreve o Cargo.lock. Ele nunca pula para uma nova versão major; para isso, você mesmo altera o Cargo.toml.

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:

  1. Downloads. Crates populares têm milhões de downloads. Isso significa que muita gente já encontrou e corrigiu os bugs antes de você.
  2. 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.
  3. 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.
  4. Versão 1.0 ou maior. Não é obrigatório, mas mostra que o autor promete uma API estável.
  5. 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.

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.

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 ligadas
▶ Experimente você mesmo

No Playground, você pode trocar de “Debug” para “Release” no menu e rodar de novo para ver a outra linha.

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ê roda
src/lib.rs
pub fn greet(name: &str) -> String {
format!("Olá, {name}!")
}
src/main.rs
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!
▶ Experimente você mesmo

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 biblioteca

Aí, 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.

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.

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"
▶ Experimente você mesmo

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.

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ído
A soma de 1 a 10 é 55
▶ Experimente você mesmo

Mude 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; o Cargo.lock registra as versões exatas que você recebeu. Faça commit dos dois.
  • cargo add nome --features x adiciona 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 check enquanto escreve, cargo fmt e cargo clippy antes do commit, e --release quando precisar de velocidade.
  • Coloque a lógica reutilizável em src/lib.rs e mantenha o src/main.rs pequeno.

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