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.
Crate e pacchetti
Sezione intitolata “Crate e pacchetti”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 dasrc/lib.rs). - Un pacchetto (package) è quello che crea
cargo new: una cartella con un fileCargo.tomle 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».
Dentro Cargo.toml
Sezione intitolata “Dentro Cargo.toml”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.nameeversionsono quello che vedrebbero gli altri se lo pubblicassi.editionsceglie 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 scrittocargo 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 veropretty_assertions = "1.4"
[profile.release]# impostazioni per `cargo build --release`opt-level = 3Aggiungere una dipendenza con cargo add
Sezione intitolata “Aggiungere una dipendenza con cargo add”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:
cargo add serde_jsonAlcuni crate hanno parti opzionali chiamate feature. Sono spente di default per tenere leggera la build. Le accendi con --features:
cargo add serde --features deriveDopo 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"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.
Requisiti di versione e Cargo.lock
Sezione intitolata “Requisiti di versione e Cargo.lock”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.locka 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 updatepassa alle versioni più recenti che rispettano ancoraCargo.tomle riscriveCargo.lock. Non salta mai a una nuova versione major: per quello devi modificare tuCargo.toml.
Trovare un buon crate
Sezione intitolata “Trovare un buon crate”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:
- I download. I crate popolari hanno milioni di download. Vuol dire che tante persone hanno trovato e corretto i bug prima di te.
- 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.
- La documentazione. Apri la sua pagina su docs.rs. I buoni crate hanno una pagina iniziale chiara con un esempio che puoi copiare.
- Versione 1.0 o superiore. Non è obbligatorio, ma ti dice che l’autore promette un’API stabile.
- 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.
I comandi Cargo essenziali
Sezione intitolata “I comandi Cargo essenziali”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.
Build di debug e di release
Sezione intitolata “Build di debug e di release”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 attiviNel Playground puoi passare da «Debug» a «Release» nel menu ed eseguirlo di nuovo per vedere l’altra riga.
Crate binario e libreria insieme
Sezione intitolata “Crate binario e libreria insieme”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 eseguipub fn greet(name: &str) -> String { format!("Ciao, {name}!")}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!Uno sguardo veloce ai workspace
Sezione intitolata “Uno sguardo veloce ai workspace”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 libreriaPoi 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.
Errori comuni
Sezione intitolata “Errori comuni”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.
Pratica
Sezione intitolata “Pratica”1. Usa un crate dal Playground
Sezione intitolata “1. Usa un crate dal Playground”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"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.
2. Messaggi solo nelle build di debug
Sezione intitolata “2. Messaggi solo nelle build di debug”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 finitoLa somma da 1 a 10 è 55Passa 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.
Riepilogo
Sezione intitolata “Riepilogo”- Cargo costruisce, esegue, testa e formatta il tuo codice, e scarica i crate da crates.io.
Cargo.tomlè quello che chiedi;Cargo.lockregistra le versioni esatte che hai ottenuto. Fai il commit di entrambi.cargo add nome --features xaggiunge 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 checkmentre scrivi,cargo fmtecargo clippyprima del commit, e--releaseper la velocità. - Metti la logica riutilizzabile in
src/lib.rse tieni piccolosrc/main.rs.
Verifica veloce
Sezione intitolata “Verifica veloce”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
Per approfondire
Sezione intitolata “Per approfondire”- The Cargo Book (in inglese): la guida e il riferimento ufficiali per tutto quello che fa Cargo.
- Specifying Dependencies — The Cargo Book (in inglese): tutti i modi di scrivere un requisito di versione, più le dipendenze da git e da percorso locale.
cargo add— The Cargo Book (in inglese): tutte le opzioni dicargo add, come--dev,--featurese--no-default-features.- More about Cargo and Crates.io — The Rust Book (in inglese): profili di release, pubblicare un tuo crate e installare strumenti con
cargo install. - Cargo Workspaces — The Rust Book (in inglese): un workspace passo passo con un binario e due librerie.