Cos'è Rust e perché tutti ne parlano
Cos'è Rust: ownership e borrow checker spiegati con esempi, la sicurezza della memoria senza garbage collector e la curva di apprendimento reale.
Ogni anno Rust vince il sondaggio del linguaggio più amato, e ogni anno la percentuale di chi lo usa davvero resta piccola. Se hai provato a capire il perché di tanto entusiasmo e hai trovato solo frasi come "sicuro e veloce", qui trovi il meccanismo concreto che sta dietro — l'ownership — spiegato con esempi, insieme al prezzo reale che chiede in cambio.
Cos'è Rust
Rust è un linguaggio compilato di sistema in cui il compilatore garantisce che il programma non possa avere errori di memoria, senza usare un garbage collector e senza costi a runtime.
Detta così sembra marketing, quindi conviene chiarire cosa significa. Nei linguaggi tradizionali hai due opzioni, entrambe con un prezzo.
Gestire la memoria a mano, come in C e C++: allochi, liberi, e sei tu responsabile. Veloce, ma sbagliare è facilissimo — usare un dato dopo averlo liberato, liberarlo due volte, leggere oltre la fine di un array. Microsoft e Google hanno entrambe pubblicato numeri simili: circa il 70% delle vulnerabilità gravi nei loro prodotti deriva da questa categoria di errori.
Affidarla a un garbage collector, come Java, Go, Python, JavaScript: un componente che periodicamente cerca i dati non più usati e li libera. Sicuro e comodo, ma costa memoria, costa tempo di CPU e introduce pause imprevedibili — un problema quando scrivi un sistema operativo o devi rispettare tempi stretti.
Rust prende una terza strada: verifica tutto al momento della compilazione. Il programma compilato non contiene nessun controllo aggiuntivo. È veloce come il C perché è codice come quello del C, solo dimostrato corretto prima di partire.
L'ownership, il concetto centrale
Tutto Rust ruota attorno a tre regole. Sono poche e si leggono in trenta secondi:
- Ogni valore ha esattamente un proprietario.
- I proprietari possono essere uno alla volta.
- Quando il proprietario esce dal suo ambito, il valore viene liberato.
Il punto tre è la chiave: la memoria si libera in un momento prevedibile e deciso dal compilatore, non da un garbage collector che passa quando gli pare.
Guarda cosa succede nella pratica:
fn main() {
let a = String::from("ciao");
let b = a; // la proprietà si sposta da a a b
println!("{}", a); // ERRORE di compilazione
}
In qualunque altro linguaggio questo funziona: a e b puntano alla stessa stringa. In Rust no. Assegnando a a b hai spostato la proprietà, e a non è più utilizzabile. Il compilatore rifiuta di procedere:
error[E0382]: borrow of moved value: `a`
Sembra ostile. In realtà sta impedendo che due parti del programma credano entrambe di possedere lo stesso dato, e che entrambe provino a liberarlo. Quel bug — il double free — in C esiste e in Rust è impossibile.
Il prestito
Spostare sempre la proprietà sarebbe impraticabile, quindi esiste il borrowing: presti il valore senza cederlo, con &.
fn lunghezza(s: &String) -> usize {
s.len()
}
fn main() {
let testo = String::from("ciao");
let n = lunghezza(&testo); // prestato, non ceduto
println!("{} ha {} caratteri", testo, n); // testo è ancora valido
}
E qui arriva la regola che elimina un'altra intera classe di bug:
Puoi avere quanti prestiti in sola lettura vuoi, oppure un solo prestito in scrittura. Mai le due cose insieme.
let mut v = vec![1, 2, 3];
let primo = &v[0]; // prestito in lettura
v.push(4); // ERRORE: serve un prestito in scrittura
println!("{}", primo);
Questo codice è rifiutato, e per un motivo sottile: push potrebbe dover riallocare il vettore altrove in memoria, e primo punterebbe a un indirizzo morto. In C++ compila senza un fiato e ti regala un bug che si manifesta a caso in produzione.
La stessa regola, applicata a più thread, rende impossibili i data race: due esecuzioni parallele non possono scrivere sullo stesso dato contemporaneamente, perché il compilatore non permette due prestiti in scrittura. Nella comunità si dice "fearless concurrency", concorrenza senza paura, e per una volta lo slogan descrive qualcosa di verificabile.
Il componente che applica tutto questo si chiama borrow checker, ed è insieme la cosa migliore e la più frustrante del linguaggio.
Combattere col compilatore: succede davvero
Va detto senza edulcorare, perché è l'esperienza reale dei primi mesi: passerai un sacco di tempo a discutere con il compilatore, e all'inizio perderai sempre.
Scrivi codice che sai essere corretto, e viene rifiutato. Leggi il messaggio d'errore — che è lungo, dettagliato e spesso suggerisce la soluzione — lo applichi, e ne salta fuori un altro. La sensazione è quella di avere un revisore pedante alle spalle che non ti lascia scrivere una riga in pace.
Con il tempo succedono due cose. La prima è che smetti di scrivere codice che il compilatore rifiuta, perché hai interiorizzato le regole. La seconda, più interessante, è che ti accorgi che una buona parte di quei rifiuti erano bug veri che in un altro linguaggio avresti scoperto dopo settimane.
Quanto ci vuole? Onestamente: per sentirsi produttivi, qualche mese di uso non sporadico. Rust non è un buon primo linguaggio — chiede di capire stack, heap, puntatori e durata dei dati, concetti che si afferrano meglio dopo aver già programmato altrove. Se parti da zero, la strada giusta è in come imparare a programmare da zero.
Quello che non ti aspetti: gli strumenti
Un motivo dell'affetto per Rust che si cita poco è che il contorno è fatto bene. cargo è insieme gestore di pacchetti, sistema di compilazione, esecutore di test e generatore di documentazione:
cargo new progetto
cargo build --release
cargo test
cargo clippy # segnala codice migliorabile
Niente da scegliere, niente da configurare. Chi arriva dal C++ o dall'ecosistema JavaScript, dove la catena di build è una scelta continua, lo nota subito.
Anche la gestione degli errori merita una riga: niente eccezioni, ma un tipo Result che devi gestire, con l'operatore ? che propaga senza cerimonie.
fn leggi_config() -> Result<String, std::io::Error> {
let contenuto = std::fs::read_to_string("config.toml")?;
Ok(contenuto)
}
Dove si usa Rust
| Ambito | Perché |
|---|---|
| Sistemi operativi, driver, firmware | Prestazioni del C senza la sua classe di bug. È entrato nel kernel Linux |
| Strumenti a riga di comando | Binario unico, velocissimo — ripgrep, fd, bat |
| Strumenti JavaScript | È il motivo per cui lo incontri senza cercarlo |
| WebAssembly | Il supporto migliore di qualsiasi linguaggio |
| Componenti critici dentro altri progetti | Un modulo Rust dentro Python o Node, dove serve velocità |
| Blockchain, motori di database | Correttezza e prestazioni non negoziabili |
Il punto sugli strumenti JavaScript merita una riga, perché è la via per cui la maggior parte delle persone incontra Rust senza averlo cercato: un numero crescente di bundler e compilatori frontend ha il motore scritto in Rust e l'interfaccia in JavaScript.
Il modello "componente critico in Rust dentro un progetto in altro linguaggio" è probabilmente il modo più sensato di adottarlo: non riscrivi tutto, riscrivi il 5% che consuma il 90% del tempo.
Rust o Go?
Sono i due linguaggi compilati del momento e vengono confrontati di continuo, ma risolvono problemi diversi.
| Go | Rust | |
|---|---|---|
| Obiettivo | Semplicità, produttività di squadra | Controllo e correttezza assoluti |
| Memoria | Garbage collector | Ownership verificata a compilazione |
| Apprendimento | Giorni | Mesi |
| Concorrenza | Goroutine, facili | Verificata dal compilatore, più rigida |
| Terreno naturale | Servizi di rete, API, infrastruttura | Sistemi, prestazioni, componenti critici |
In una frase: Go per i servizi, Rust dove prestazioni e correttezza contano più del tempo di sviluppo. Per un'API che deve stare in piedi e reggere carico, Go ti porta in produzione prima. Per un motore di database, un componente del kernel o un modulo dove una pausa del garbage collector è inaccettabile, Rust è la risposta giusta.
Scegliere Rust per un normale servizio web perché "è più veloce" è quasi sempre un errore di valutazione: il collo di bottiglia sarà il database, non il linguaggio.
I limiti onesti
La curva di apprendimento è il costo maggiore, e vale sia per te che per chi entra nel progetto dopo.
I tempi di compilazione sono lunghi. Un progetto medio impiega minuti, non secondi. Chi viene da Go lo sente parecchio.
Certe strutture dati sono sgradevoli da scrivere. Liste concatenate, grafi, alberi con riferimenti all'indietro: ciò che altrove è un esercizio da principianti qui richiede Rc, RefCell o codice unsafe.
Non serve quasi mai per il web applicativo, gli script o i prototipi. Se il progetto vive di iterazioni rapide, il rigore del compilatore rema contro.
Trovare lavoro in Rust in Italia è difficile. Le posizioni esistono ma sono poche e spesso richiedono esperienza in ambiti specifici. Per una lettura del mercato reale vedi i linguaggi più richiesti in Italia.
Errori comuni dei primi mesi
Riempire il codice di .clone() per zittire il compilatore. Funziona, compila, ed è il modo più rapido per buttare via le prestazioni che eri venuto a cercare. Va bene come stampella temporanea, non come stile.
Usare unwrap() ovunque. Va bene in un esempio, non in codice che gira davvero: significa "se qui c'è un errore, muori". Gestisci il Result.
Combattere il borrow checker invece di ascoltarlo. Nove volte su dieci sta segnalando un problema reale di progettazione dei dati. Ristruttura invece di aggirare.
Iniziare con un progetto troppo ambizioso. Parti da uno strumento a riga di comando piccolo — vedi progetti per imparare a programmare — non da un motore di rendering.
In sintesi
Rust risolve un problema vero: la sicurezza della memoria senza garbage collector, verificata dal compilatore invece che sperata dal programmatore. Non è un'ottimizzazione di dettaglio, elimina la categoria di bug che genera la maggioranza delle vulnerabilità gravi.
Il meccanismo è l'ownership: un proprietario per valore, liberazione alla fine dell'ambito, prestiti in lettura multipli oppure uno solo in scrittura. Da queste tre regole discendono l'assenza di use-after-free e l'impossibilità dei data race.
Il prezzo è il tempo. Mesi per essere produttivi, compilazioni lente, e certe strutture dati che chiedono molta più fatica del dovuto.
Quando ha senso: sistemi, strumenti a riga di comando, WebAssembly, e il componente critico dentro un progetto scritto in altro. Quando non ha senso: applicazioni web ordinarie, script, prototipi.
Per collocarlo rispetto a tutto il resto, la mappa completa è in tutti i linguaggi di programmazione; se devi ancora decidere da dove partire, la risposta ragionata è in quale linguaggio imparare nel 2026.