Salta ai contenuti

Cargo e crate in Rust

Nella pagina Per iniziare hai usato cargo new e cargo run. Era solo l’inizio. Cargo è lo strumento di build di Rust e il suo gestore di pacchetti. Compila il tuo codice, esegue i test, formatta i file e scarica le librerie scritte da altre persone.

Queste librerie si chiamano crate. Pensa ai crate come alle app di un app store: qualcun altro ha già risolto un problema (leggere JSON, fare richieste web, interpretare date), e tu puoi aggiungere la sua soluzione al tuo progetto con un solo comando. Questa pagina ti mostra come funziona, e come scegliere un buon crate.

Ci sono due parole che tornano di continuo:

  • Un crate è un’unità di codice Rust che il compilatore costruisce. È o un binario (un programma che puoi eseguire, che parte da src/main.rs) o una libreria (codice che usano altri programmi, che parte da src/lib.rs).
  • Un pacchetto (package) è quello che crea cargo new: una cartella con un file Cargo.toml e uno o più crate dentro.

Nel parlare di tutti i giorni si dice «crate» per entrambi. Quando qualcuno dice «usa il crate serde», intende «aggiungi al tuo progetto il pacchetto serde da crates.io».

Ogni pacchetto ha un file Cargo.toml. È scritto in TOML (in inglese), un formato semplice per le impostazioni. Ecco cosa ti dà cargo new hello_rust:

[package]
name = "hello_rust"
version = "0.1.0"
edition = "2024"
[dependencies]
  • [package] descrive il tuo progetto. name e version sono quello che vedrebbero gli altri se lo pubblicassi.
  • edition sceglie quale edizione del linguaggio Rust usare. Le edizioni (2018, 2021, 2024) permettono a Rust di migliorare senza rompere il codice vecchio. Tieni semplicemente quella che ha scritto cargo new.
  • [dependencies] elenca i crate che usa il tuo codice. All’inizio è vuota.

Man mano che un progetto cresce, potresti vedere altre sezioni:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[dev-dependencies]
# usato solo da test ed esempi, non dal programma vero
pretty_assertions = "1.4"
[profile.release]
# impostazioni per `cargo build --release`
opt-level = 3

Puoi modificare [dependencies] a mano, ma il modo più facile è cargo add. Trova l’ultima versione su crates.io e scrive la riga al posto tuo:

Terminal window
cargo add serde_json

Alcuni crate hanno parti opzionali chiamate feature. Sono spente di default per tenere leggera la build. Le accendi con --features:

Terminal window
cargo add serde --features derive

Dopo questi due comandi, il tuo Cargo.toml conterrà qualcosa del genere:

[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"

Ora usa il crate nel tuo codice. Il prossimo cargo run lo scarica, lo compila una volta e lo collega al tuo programma:

use serde_json::json;
fn main() {
let crab = json!({ "legs": 10, "name": "Ferris" });
println!("{}", crab);
println!("Nome: {}", crab["name"]);
}

Output:

{"legs":10,"name":"Ferris"}
Nome: "Ferris"
▶ Provalo tu

Le virgolette intorno a "Ferris" ci sono perché crab["name"] è ancora un valore JSON, non una semplice stringa Rust.

Il Rust Playground include già i crate più popolari, quindi lì questo codice funziona senza cargo add. Sul tuo computer, invece, ti serve prima il passaggio cargo add serde_json. Imparerai molto di più su questo crate nella pagina JSON con Serde.

Per togliere un crate che non ti serve più, esegui cargo remove serde_json.

Quando scrivi serde_json = "1.0", non stai chiedendo esattamente la versione 1.0. Stai dicendo «qualsiasi versione compatibile con la 1.0». Cargo segue il versionamento semantico (SemVer): una versione è MAJOR.MINOR.PATCH, e solo un cambio del numero major ha il permesso di rompere il tuo codice.

Scrivi Cargo può usare Perché
"1.0" (uguale a "^1.0") >=1.0.0 e <2.0.0 le versioni 1.x dovrebbero essere tutte compatibili
"1.4.2" >=1.4.2 e <2.0.0 almeno la 1.4.2, ma sempre 1.x
"0.4" >=0.4.0 e <0.5.0 prima della 1.0, è il numero minor che può rompere
"~1.4" >=1.4.0 e <1.5.0 solo aggiornamenti patch
"=1.4.2" esattamente 1.4.2 serve raramente

Allora chi decide la versione esatta? È il compito di Cargo.lock. La prima volta che fai la build, Cargo sceglie le versioni più recenti che rispettano i tuoi requisiti e le scrive in Cargo.lock. Ogni build successiva usa esattamente le stesse versioni, sul tuo computer e su quello di tutti gli altri.

  • Non modificare Cargo.lock a mano. Lo gestisce Cargo.
  • Fanne il commit su git, soprattutto per i programmi. Così i tuoi colleghi e la tua CI costruiscono esattamente quello che hai costruito tu.
  • cargo update passa alle versioni più recenti che rispettano ancora Cargo.toml e riscrive Cargo.lock. Non salta mai a una nuova versione major: per quello devi modificare tu Cargo.toml.

Ci sono tre siti (in inglese) che userai di continuo:

  • crates.io è il registro ufficiale. Ci vive ogni crate pubblico. Cerca una parola come «csv» o «date».
  • docs.rs ospita la documentazione di ogni crate, generata in automatico. Per esempio, docs.rs/serde_json.
  • lib.rs è un indice non ufficiale e più veloce, che raggruppa i crate per categoria. È ottimo per curiosare.

Tanti crate risolvono lo stesso problema, quindi come scegli? Prima di aggiungerne uno, prenditi un minuto per controllare:

  1. I download. I crate popolari hanno milioni di download. Vuol dire che tante persone hanno trovato e corretto i bug prima di te.
  2. Gli aggiornamenti recenti. Guarda la data dell’ultima release. Un crate che non cambia da anni potrebbe andare benissimo lo stesso (alcuni sono semplicemente «finiti»), ma controlla che il suo repository non sia pieno di issue senza risposta.
  3. La documentazione. Apri la sua pagina su docs.rs. I buoni crate hanno una pagina iniziale chiara con un esempio che puoi copiare.
  4. Versione 1.0 o superiore. Non è obbligatorio, ma ti dice che l’autore promette un’API stabile.
  5. Poche dipendenze. Ogni crate che aggiungi porta con sé le sue dipendenze. Più è piccolo, più è facile fidarsi.

Nel dubbio, cerca «rust» più il tuo problema e guarda quale crate la community consiglia più spesso.

Non serve impararli a memoria. Tieni questa tabella a portata di mano e li imparerai usandoli:

Comando Che cosa fa
cargo new my_app Crea un nuovo progetto binario (aggiungi --lib per una libreria)
cargo build Compila in target/debug/
cargo run Compila ed esegue (gli argomenti vanno dopo --: cargo run -- input.txt)
cargo check Cerca errori senza costruire un programma. Molto più veloce di build
cargo test Esegue i tuoi test (vedi Test)
cargo fmt Formatta tutto il codice nello stile standard
cargo clippy Esegue il linter, che suggerisce codice più semplice e idiomatico
cargo doc --open Genera la documentazione HTML del progetto e di ogni dipendenza, poi la apre
cargo update Aggiorna Cargo.lock alle versioni compatibili più recenti
cargo build --release Compila un programma ottimizzato in target/release/

Una buona abitudine: esegui spesso cargo check mentre scrivi, e cargo fmt e cargo clippy prima di fare commit. Anche cargo doc --open è una perla nascosta: ti dà la documentazione delle versioni esatte dei crate che usi, e funziona anche offline.

Di default, Cargo fa una build di debug. Compila in fretta e mantiene dei controlli di sicurezza in più, come andare in panic quando un intero va in overflow. È perfetta mentre sviluppi.

Una build di release (cargo build --release o cargo run --release) ci mette di più a compilare ma gira molto più veloce, spesso 10 volte o più. Usala quando distribuisci il programma o ne misuri la velocità.

Il tuo codice può perfino capire che tipo di build è:

fn main() {
if cfg!(debug_assertions) {
println!("Build di debug: i controlli in più sono attivi");
} else {
println!("Build di release: ottimizzata per la velocità");
}
}

Output:

Build di debug: i controlli in più sono attivi
▶ Provalo tu

Nel Playground puoi passare da «Debug» a «Release» nel menu ed eseguirlo di nuovo per vedere l’altra riga.

Un pacchetto può contenere una libreria e un binario nello stesso momento. È una struttura molto comune. Tieni la logica vera in src/lib.rs, e src/main.rs resta piccolo e si limita a chiamarla:

greeter/
├── Cargo.toml
└── src/
├── lib.rs # il crate libreria: la logica riutilizzabile
└── main.rs # il crate binario: il programma che esegui
src/lib.rs
pub fn greet(name: &str) -> String {
format!("Ciao, {name}!")
}
src/main.rs
fn main() {
// la libreria ha il nome del pacchetto
println!("{}", greeter::greet("Ferris"));
}

Perché dividerlo così? Il codice in lib.rs è facile da testare, e altri programmi (o un secondo binario) possono riutilizzarlo. Il Playground ha un solo file, ma puoi ottenere un effetto simile con un pub mod, come nella pagina sui Moduli:

mod greeter {
pub fn greet(name: &str) -> String {
format!("Ciao, {name}!")
}
}
fn main() {
println!("{}", greeter::greet("Ferris"));
}

Output:

Ciao, Ferris!
▶ Provalo tu

Quando un progetto cresce e diventa più pacchetti che vanno insieme (per esempio un’app da riga di comando, un server web e una libreria condivisa), puoi metterli in un unico workspace. Condividono un solo Cargo.lock e una sola cartella target/, quindi tutto viene costruito insieme e usa le stesse versioni delle dipendenze.

Il Cargo.toml principale elenca i membri:

[workspace]
members = ["app", "core"]
resolver = "3"
my_project/
├── Cargo.toml # il file del workspace qui sopra
├── Cargo.lock # condiviso da tutti i membri
├── app/ # un pacchetto binario
└── core/ # un pacchetto libreria

Poi cargo build nella cartella principale costruisce tutto, e cargo run -p app esegue un singolo membro. Per i tuoi primi progetti non ti servirà, ma è bene riconoscerlo quando leggi il codice di altri.

Usare un crate senza averlo aggiunto. Se scrivi use serde_json::json; ma hai dimenticato cargo add serde_json, il compilatore non lo trova:

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`

Il messaggio dice che l’import serde_json non è risolto e che forse ti manca un crate con quel nome. La soluzione è eseguire cargo add serde_json. Controlla anche come l’hai scritto: i nomi dei crate usano - o _, e nel codice un - diventa sempre _.

Dimenticare una feature. Se #[derive(Serialize)] dice di non trovare la macro derive, probabilmente hai aggiunto serde senza --features derive. Esegui di nuovo cargo add serde --features derive: aggiorna la riga che c’è già.

Misurare la velocità con una build di debug. Se il tuo programma ti sembra lento, prova cargo run --release prima di modificare il codice.

Usa la macro json! di serde_json per descrivere un crate con tre chiavi: downloads (un numero), name e version. Stampa il valore intero, poi stampa solo la versione.

Suggerimento: copia la forma dell’esempio crab in Aggiungere una dipendenza con cargo add. Sul tuo computer, esegui prima cargo add serde_json; il Playground ce l’ha già.

Mostra la soluzione
use serde_json::json;
fn main() {
let krate = json!({
"downloads": 500,
"name": "serde_json",
"version": "1.0"
});
println!("{}", krate);
println!("Versione: {}", krate["version"]);
}

Output:

{"downloads":500,"name":"serde_json","version":"1.0"}
Versione: "1.0"
▶ Provalo tu

La variabile si chiama krate perché crate è una parola riservata in Rust. Questa grafia è una battuta classica nel codice Rust. Come prima, la versione viene stampata tra virgolette perché è ancora un valore JSON; la pagina JSON con Serde mostra come estrarre il testo semplice.

Scrivi una funzione debug_log(msg: &str) che stampa [debug] seguito dal messaggio, ma solo in una build di debug. Usala in main intorno a un piccolo calcolo, e stampa il risultato normalmente.

Suggerimento: racchiudi il println! in if cfg!(debug_assertions) { ... }, come in Build di debug e di release.

Mostra la soluzione
fn debug_log(msg: &str) {
if cfg!(debug_assertions) {
println!("[debug] {msg}");
}
}
fn main() {
debug_log("inizio del calcolo");
let mut total = 0;
for n in 1..=10 {
total += n;
}
debug_log("calcolo finito");
println!("La somma da 1 a 10 è {total}");
}

Output:

[debug] inizio del calcolo
[debug] calcolo finito
La somma da 1 a 10 è 55
▶ Provalo tu

Passa il Playground a «Release» (o usa cargo run --release in locale) ed eseguilo di nuovo: viene stampata solo l’ultima riga. Per i progetti veri, la pagina sul Logging mostra dei crate che lo fanno per bene, con livelli come debug e info.

  • Cargo costruisce, esegue, testa e formatta il tuo codice, e scarica i crate da crates.io.
  • Cargo.toml è quello che chiedi; Cargo.lock registra le versioni esatte che hai ottenuto. Fai il commit di entrambi.
  • cargo add nome --features x aggiunge una dipendenza. "1.0" significa «qualsiasi versione 1.x compatibile».
  • Giudica un crate da download, aggiornamenti recenti e buona documentazione su docs.rs.
  • Usa cargo check mentre scrivi, cargo fmt e cargo clippy prima del commit, e --release per la velocità.
  • Metti la logica riutilizzabile in src/lib.rs e tieni piccolo src/main.rs.

Verifica veloce

1. In Cargo.toml, che cosa significa serde_json = "1.0"?

2. Quale file registra le versioni esatte di ogni dipendenza usata dalla tua build?

3. Vuoi controllare gli errori nel codice il più velocemente possibile, senza produrre un programma. Quale comando è il più adatto?

4. Come aggiungi serde con la feature derive attiva?

Punteggio: 0 / 4