Dall'esperimento di Graydon Hoare nel 2006 all'ecosistema Rust di oggi: come la sicurezza della memoria senza garbage collector ha ridefinito la programmazione di sistema.

Questo articolo racconta una storia d'origine mirata: come l'esperimento personale di Graydon Hoare è diventato Rust, e perché le scelte progettuali di Rust sono state importanti al punto da ridefinire le aspettative nella programmazione di sistema.
La “programmazione di sistema” sta vicino alla macchina—e ai rischi del tuo prodotto. La trovi nei browser, nei motori di gioco, nei componenti di sistema operativo, nei database, nel networking e nel software embedded—luoghi dove di solito serve:
Storicamente, questa combinazione ha spinto i team verso C e C++, insieme a regole, revisioni e tool approfonditi per ridurre i bug legati alla memoria.
La promessa di Rust è semplice da dire e difficile da realizzare:
Sicurezza della memoria senza garbage collector.
Rust mira a prevenire guasti comuni come use-after-free, double-free e molte forme di data race—senza affidarsi a un runtime che periodicamente sospende il programma per recuperare memoria. Invece, Rust sposta gran parte di quel lavoro al tempo di compilazione tramite ownership e borrowing.
Troverai la storia (dalle idee iniziali all'ingresso di Mozilla) e i concetti chiave (ownership, borrowing, lifetimes, safe vs unsafe) spiegati in linguaggio semplice.
Quello che non troverai è un tutorial completo su Rust, un tour esaustivo della sintassi o una guida passo-passo per il setup di un progetto. Considera questo testo come il “perché” dietro il design di Rust, con esempi sufficienti a rendere le idee concrete.
Nota dell'autore: il pezzo completo è pensato per ~3.000 parole, lasciando spazio a esempi brevi senza trasformarsi in un manuale di riferimento.
Rust non è nato come un linguaggio creato da comitato per “sconfiggere” C++. È iniziato come un esperimento personale di Graydon Hoare nel 2006—lavoro che ha portato avanti in modo indipendente prima di attirare attenzione più ampia. Quest'origine conta: molte decisioni progettuali iniziali sembrano tentativi di risolvere dolori quotidiani, non di “vincere” la teoria dei linguaggi.
Hoare stava esplorando come scrivere software di basso livello e ad alte prestazioni senza ricorrere al garbage collector—evitando al contempo le cause più comuni di crash e bug di sicurezza in C e C++. La tensione è familiare ai programmatori di sistema:
La direzione “sicurezza della memoria senza GC” non era all'inizio uno slogan di marketing. Era un obiettivo di design: mantenere caratteristiche di performance adatte al lavoro di sistemi, ma rendere molte categorie di bug di memoria difficili da esprimere.
È lecito chiedersi perché non bastasse “un compilatore migliore” per C/C++. Strumenti come analisi statica, sanitizzatori e librerie più sicure prevengono molti problemi, ma generalmente non possono garantire la sicurezza della memoria. I linguaggi sottostanti permettono pattern che sono difficili—o impossibili—a controllare completamente dall'esterno.
La scommessa di Rust è stata spostare regole chiave nel linguaggio e nel sistema di tipi così che la sicurezza diventi un risultato predefinito, pur consentendo controllo manuale in vie d'uscita chiaramente segnate.
Alcuni dettagli sui primi giorni di Rust circolano come aneddoti (spesso ripetuti in talk e interviste). Nel raccontare questa storia d'origine aiuta separare le tappe ampiamente documentate—come l'inizio nel 2006 e l'adozione successiva in Mozilla Research—da ricordi personali e rielaborazioni secondarie.
Per fonti primarie, cerca documentazione e appunti di design delle prime versioni di Rust, talk/interviste di Graydon Hoare e post dell'era Mozilla/Servo che descrivono perché il progetto è stato adottato e come sono stati definiti gli obiettivi. Una sezione “ulteriori letture” può indirizzare il lettore a quegli originali (vedi /blog per link correlati).
La programmazione di sistema spesso significa lavorare vicino all'hardware. Questa vicinanza rende il codice veloce ed efficiente nelle risorse. Rende però anche gli errori di memoria particolarmente punitivi.
Alcuni bug classici si ripetono spesso:
Questi errori non sono sempre evidenti. Un programma può “funzionare” per settimane e poi crashare solo sotto un raro pattern di input o tempistica.
I test dimostrano che qualcosa funziona per i casi che hai provato. I bug di memoria spesso si nascondono nei casi che non hai coperto: input insoliti, hardware diverso, lievi cambi di tempistica o una nuova versione del compilatore. Possono inoltre essere non deterministici—soprattutto in programmi multithread—quindi il bug scompare nel momento in cui aggiungi logging o attacchi un debugger.
Quando la memoria va male, non ottieni solo un errore pulito. Ottieni stato corrotto, crash imprevedibili e vulnerabilità di sicurezza che gli attaccanti cercano attivamente. I team spendono enorme sforzo inseguendo guasti difficili da riprodurre e ancor più difficili da diagnosticare.
Il software di basso livello non può sempre “pagare” la sicurezza con controlli runtime pesanti o scansioni costanti della memoria. L'obiettivo è più simile a prendere in prestito uno strumento da un'officina condivisa: puoi usarlo liberamente, ma le regole devono essere chiare—chi lo detiene, chi può condividerlo e quando va restituito. I linguaggi di sistema tradizionalmente lasciavano quelle regole alla disciplina umana. La storia d'origine di Rust inizia mettendo in discussione quel compromesso.
Il garbage collection (GC) è un modo comune con cui i linguaggi prevengono i bug di memoria. Invece di chiederti di liberare la memoria manualmente, il runtime traccia quali oggetti sono ancora raggiungibili e recupera il resto automaticamente. Questo elimina intere categorie di problemi—use-after-free, double free e molte perdite—perché il programma non “dimentica” di liberare nello stesso modo.
Il GC non è “cattivo”, ma cambia il profilo di performance di un programma. La maggior parte dei collector introduce una combinazione di:
Per molte applicazioni—backend web, software aziendale, tool—questi costi sono accettabili o invisibili. I GC moderni sono eccellenti e aumentano notevolmente la produttività degli sviluppatori.
Nella programmazione di sistema spesso conta il peggior caso. Un motore di browser ha bisogno di rendering fluido; un controller embedded può avere vincoli temporali stringenti; un server a bassa latenza può essere ottimizzato per mantenere bassa la tail latency sotto carico. In questi ambienti, “di solito veloce” può valere meno di “costantemente prevedibile”.
La grande promessa di Rust è: mantieni il controllo simile a C/C++ sulla memoria e il layout dei dati, ma fornisci sicurezza della memoria senza affidarsi a un garbage collector. L'obiettivo è ottenere caratteristiche di performance prevedibili—pur facendo sì che il codice sicuro sia l'impostazione predefinita.
Questo non è un argomento che il GC sia inferiore. È la scommessa che esista un ampio e importante terreno intermedio: software che necessita di controllo di basso livello e garanzie moderne di sicurezza.
L'ownership è l'idea più semplice e centrale di Rust: ogni valore ha un singolo proprietario responsabile della sua pulizia quando non è più necessario.
Quella singola regola sostituisce molta della contabilità manuale “chi libera questa memoria?” che i programmatori C/C++ tengono spesso in testa. Invece di contare sulla disciplina, Rust rende la pulizia prevedibile.
Quando copi qualcosa, ottieni due versioni indipendenti. Quando muovi qualcosa, passi l'originale—dopo il move, la vecchia variabile non può più usarlo.
Rust tratta molti valori allocati sull'heap (come stringhe, buffer o vettori) come movimenti per default. Copiarli incautamente può essere costoso e, più importante, confondere: se due variabili credono di “possedere” la stessa allocazione, hai creato le condizioni per bug di memoria.
Ecco l'idea in pseudo-codice:
buffer = make_buffer()
ownerA = buffer // ownerA lo possiede
ownerB = ownerA // muovi la proprietà a ownerB
use(ownerA) // non consentito: ownerA non possiede più nulla
use(ownerB) // ok
// quando ownerB termina, buffer viene pulito automaticamente
Poiché esiste sempre esattamente un proprietario, Rust sa esattamente quando un valore deve essere pulito: quando il suo proprietario esce dallo scope. Questo significa gestione automatica della memoria (non chiami free() ovunque) senza dover ricorrere a un garbage collector per scandire il programma e recuperare memoria inutilizzata.
Questa regola di ownership blocca una vasta classe di problemi classici:
Il modello di ownership di Rust non si limita a incoraggiare abitudini più sicure: rende molti stati insicuri non rappresentabili, che è la base su cui si costruiscono le restanti caratteristiche di sicurezza di Rust.
L'ownership spiega chi “possiede” un valore. Il borrowing spiega come altre parti del programma possono temporaneamente usare quel valore senza portarselo via.
Quando prendi in prestito qualcosa in Rust, ottieni un riferimento. Il proprietario originale resta responsabile di liberare la memoria; il prenditore ha solo il permesso di usarla per un certo periodo.
Rust ha due tipi di borrow:
&T): accesso in sola lettura.&mut T): accesso in lettura-scrittura.La regola centrale del borrowing è semplice da enunciare e potente nella pratica:
Quella regola previene una comune classe di bug: una parte del programma legge dati mentre un'altra li modifica sotto i piedi.
Un riferimento è sicuro solo se non sopravvive mai a ciò a cui punta. Rust chiama quella durata un lifetime—l'intervallo di tempo durante il quale il riferimento è garantito valido.
Non serve formalismo per usare questa idea: un riferimento non deve restare in giro dopo che il suo proprietario è sparito.
Rust applica queste regole a tempo di compilazione tramite il borrow checker. Invece di sperare che i test scovino un cattivo riferimento o una mutazione rischiosa, Rust rifiuta di costruire codice che potrebbe usare la memoria in modo errato.
Pensa a un documento condiviso:
La concorrenza è dove i bug “funziona sulla mia macchina” vanno a nascondersi. Quando due thread eseguono contemporaneamente, possono interagire in modi sorprendenti—soprattutto quando condividono dati.
Una data race accade quando:
Il risultato non è solo un output sbagliato. Le data race possono corrompere lo stato, far crashare i programmi o creare vulnerabilità di sicurezza. Peggio, possono essere intermittenti: un bug può scomparire quando aggiungi logging o esegui sotto debugger.
Rust prende una posizione non comune: invece di fidarsi che ogni sviluppatore si ricordi le regole ogni volta, cerca di rendere molti pattern di concorrenza insicuri non rappresentabili nel codice sicuro.
A un livello alto, le regole di ownership e borrowing non si fermano al codice single-thread. Modellano anche cosa è permesso condividere tra thread. Se il compilatore non può dimostrare che l'accesso condiviso è coordinato, non lascerà compilare il codice.
Questo è ciò che la gente intende per “concorrenza sicura” in Rust: scrivi ancora programmi concorrenti, ma una categoria intera di errori tipo “ehm, due thread hanno scritto la stessa cosa” viene catturata prima che il programma giri.
Immagina due thread che incrementano lo stesso contatore:
Rust non proibisce trucchi di concorrenza di basso livello. Li quarantena. Se davvero hai bisogno di fare qualcosa che il compilatore non può verificare, puoi usare blocchi unsafe, che funzionano come etichette di avvertimento: “qui serve responsabilità umana”. Questa separazione mantiene gran parte del codice in un sottoinsieme più sicuro, permettendo comunque potenza di basso livello dove giustificata.
La reputazione di Rust come linguaggio sicuro può sembrare assoluta, ma è più accurato dire che Rust rende esplicito—e più facile da controllare—il confine tra programmazione sicura e non.
La maggior parte del codice è “safe Rust”. Qui il compilatore impone regole che impediscono i bug di memoria più comuni: use-after-free, double free, puntatori dangling e data race. Puoi comunque scrivere logica sbagliata, ma non puoi accidentalmente violare la sicurezza della memoria con le caratteristiche normali del linguaggio.
Un punto chiave: safe Rust non è “Rust più lento”. Molti programmi ad alte prestazioni sono scritti interamente in safe Rust perché il compilatore può ottimizzare aggressivamente una volta che può fidarsi che le regole siano rispettate.
"Unsafe" esiste perché la programmazione di sistema a volte richiede capacità che il compilatore non può provare sicure in generale. Ragioni tipiche includono:
Usare unsafe non spegne tutti i controlli. Permette solo un insieme ristretto di operazioni (come dereferenziare puntatori raw) che altrimenti sarebbero vietate.
Rust ti costringe a marcare blocchi e funzioni unsafe, rendendo il rischio visibile durante la revisione del codice. Un pattern comune è mantenere un piccolo “core unsafe” incapsulato in un'API sicura, così la maggior parte del programma resta in safe Rust mentre una parte ristretta e ben definita mantiene le invarianti necessarie.
Tratta unsafe come uno strumento potente:
Usato bene, l'unsafe diventa un'interfaccia controllata alle parti della programmazione di sistema che richiedono ancora precisione manuale—senza rinunciare ai benefici di sicurezza di Rust nel resto del codice.
Rust non è diventato “reale” solo perché aveva idee intelligenti su carta—è diventato reale perché Mozilla ha messo quelle idee sotto pressione.
Mozilla Research cercava modi per costruire componenti critici di browser con meno bug di sicurezza. I motori di browser sono notoriamente complessi: parsano input non fidati, gestiscono grandi quantità di memoria e eseguono carichi molto concorrenti. Questa combinazione rende i difetti di sicurezza e le condizioni di race comuni e costose.
Supportare Rust si allineava a quell'obiettivo: mantenere la velocità della programmazione di sistema riducendo intere classi di vulnerabilità. L'impegno di Mozilla ha anche segnalato al mondo che Rust non era solo l'esperimento personale di Graydon Hoare, ma un linguaggio che poteva essere testato su uno dei codebase più difficili al mondo.
Servo—il motore browser sperimentale—è diventato un luogo ad alta visibilità per provare Rust su scala. Lo scopo non era “vincere” il mercato dei browser. Servo è stato un laboratorio dove valutare feature del linguaggio, diagnostica del compilatore e tooling con vincoli reali: tempi di build, supporto cross-platform, esperienza sviluppatore, tuning delle performance e correttezza sotto parallelismo.
Ancora più importante, Servo ha contribuito a plasmare l'ecosistema intorno al linguaggio: librerie, tool di build, convenzioni e pratiche di debug che contano quando si supera il livello dei programmi dimostrativi.
I progetti reali creano cicli di feedback che il design linguistico non può simulare. Quando gli ingegneri incontrano frizioni—messaggi d'errore poco chiari, pezzi mancanti nella libreria, pattern scomodi—quei punti dolenti emergono rapidamente. Nel tempo quella pressione costante ha aiutato Rust a maturare da concetto promettente a qualcosa di affidabile per software grandi e critici.
Se vuoi esplorare l'evoluzione di Rust dopo questa fase, vedi /blog/rust-memory-safety-without-gc.
Rust si posiziona in un terreno intermedio: mira alle performance e al controllo che ci si aspetta da C e C++, ma prova a eliminare una grande classe di bug che quei linguaggi lasciano alla disciplina, ai test e alla fortuna.
In C e C++ gli sviluppatori gestiscono la memoria direttamente—allocano, liberano e garantiscono che i puntatori rimangano validi. Questa libertà è potente, ma rende facile creare use-after-free, double-free, buffer overflow e bug sottili sui lifetime. Il compilatore in genere si fida di te.
Rust capovolge la relazione. Mantieni ancora il controllo di basso livello (scelte stack vs heap, layout prevedibile, trasferimenti di proprietà espliciti), ma il compilatore impone regole su chi possiede un valore e per quanto tempo i riferimenti possono vivere. Invece di “stai attento con i puntatori”, Rust dice “prova la sicurezza al compilatore”, e non compilerà codice che possa infrangere quelle garanzie nel codice sicuro.
I linguaggi con garbage collector (come Java, Go, C# o molti scripting language) scambiano la gestione manuale della memoria per comodità: gli oggetti vengono liberati automaticamente quando non sono più raggiungibili. Questo può essere un grande boost di produttività.
La promessa di Rust—“sicurezza della memoria senza GC”—significa che non paghi per un collector a runtime, il che aiuta quando ti servono controllo stretto sulla latenza, sull'occupazione di memoria, sul tempo di avvio o quando giri in ambienti con risorse limitate. Il compromesso è che modelli ownership esplicitamente e lasci che il compilatore lo applichi.
Rust può sembrare più difficile all'inizio perché insegna un nuovo modello mentale: pensi in termini di ownership, borrowing e lifetimes, non solo “passa un puntatore e spera vada bene”. La frizione iniziale spesso emerge quando si modellano stati condivisi complessi o grafi di oggetti intricati.
Rust tende a brillare per team che costruiscono software sensibile alla sicurezza e critico per le prestazioni—browser, networking, crittografia, embedded, servizi backend con requisiti stringenti di affidabilità. Se il tuo team privilegia la massima velocità di iterazione rispetto al controllo a basso livello, un linguaggio con GC può ancora essere la scelta migliore.
Rust non è una sostituzione universale; è una forte opzione quando vuoi prestazioni alla C/C++ con garanzie di sicurezza su cui puoi fare affidamento.
Rust non ha attirato attenzione solo perché era “un C++ più bello”. Ha cambiato la conversazione insistendo che il codice di basso livello può essere veloce, sicuro per la memoria ed esplicito sui costi allo stesso tempo.
Prima di Rust, i team spesso consideravano i bug di memoria come una tassa da pagare per le prestazioni, poi contavano su test, code review e fix post-incidente per gestire il rischio. Rust ha fatto una scommessa diversa: codificare regole comuni (chi possiede i dati, chi può mutarli, quando devono restare validi) nel linguaggio in modo che intere categorie di bug vengano rifiutate a compilazione.
Questo cambiamento è importante perché non chiedeva agli sviluppatori di essere “perfetti”. Chiedeva loro di essere chiari—e poi lasciare che il compilatore imponesse quella chiarezza.
L'influenza di Rust appare in un mix di segnali più che in un singolo annuncio: interesse crescente da parte di aziende che rilasciano software sensibile alle prestazioni, maggiore presenza nei corsi universitari e tooling che sembra meno “progetto di ricerca” e più “strumento quotidiano” (gestione pacchetti, formattazione, linting e workflow di documentazione che funzionano fuori dalla scatola).
Niente di tutto ciò significa che Rust sia sempre la scelta migliore—ma vuol dire che la sicurezza per default è ora un'aspettativa realistica, non un lusso.
Rust viene spesso valutato per:
“Nuovo standard” non vuol dire che ogni sistema sarà riscritto in Rust. Significa che l'asticella si è alzata: i team chiedono sempre più spesso, Perché accettare default non sicuri sulla memoria quando non è necessario? Anche quando Rust non viene adottato, il suo modello ha spinto l'ecosistema a valorizzare API più sicure, invarianti più chiare e tooling migliore per la correttezza.
Se vuoi altri retroscena di ingegneria come questo, sfoglia /blog per post correlati.
La storia d'origine di Rust ha un filo semplice: il progetto personale di una persona (Graydon Hoare che sperimenta un nuovo linguaggio) si è scontrato con un problema ostinato nella programmazione di sistema, e la soluzione si è rivelata sia rigorosa che pratica.
Rust ha riformulato un compromesso che molti sviluppatori davano per inevitabile:
Il cambiamento pratico non è solo “Rust è più sicuro”. È che la sicurezza può essere una proprietà predefinita del linguaggio, invece che una disciplina applicata a colpi di code review e test.
Se sei curioso, non serve una riscrittura enorme per capire come si sente Rust.
Inizia con poco:
Per un percorso graduale, scegli una “fetta sottile” come obiettivo—ad es. “leggi un file, trasformalo, scrivi l'output”—e concentrati sul codice chiaro piuttosto che sul codice furbo.
Se stai prototipando un componente Rust dentro un prodotto più grande, può aiutare muovere velocemente le parti circostanti (UI amministrativa, dashboard, plane di controllo, API semplici) mentre mantieni la logica di sistema rigorosa. Piattaforme come Koder.ai possono accelerare quel tipo di sviluppo “di collegamento” tramite workflow guidati dalla chat—permettendoti di generare rapidamente un front end React, un backend Go e uno schema PostgreSQL, poi esportare il codice e integrare il servizio Rust su confini puliti.
Se vuoi un secondo post, cosa ti sarebbe più utile?
unsafe viene usato responsabilmente in progetti realiRispondi con il tuo contesto (cosa costruisci, quale linguaggio usi ora e cosa ottimizzi), e adatterò la prossima sezione a quello.
La programmazione di sistema è lavoro che sta vicino all'hardware e alle superfici ad alto rischio del prodotto—come motori di browser, database, componenti di sistema operativo, networking e software embedded.
Tipicamente richiede performance prevedibili, controllo di basso livello sulla memoria/disposizione dei dati e alta affidabilità, dove crash e bug di sicurezza sono particolarmente costosi.
Significa che Rust mira a prevenire bug di memoria comuni (come use-after-free e double-free) senza affidarsi a un garbage collector a runtime.
Invece di far eseguire al collector la scansione e la riconquista della memoria durante l'esecuzione, Rust sposta molti controlli di sicurezza al tempo di compilazione tramite le regole di ownership e borrowing.
Strumenti come sanitizzatori e analisi statica possono rilevare molti problemi, ma in genere non possono garantire la sicurezza della memoria quando il linguaggio permette liberamente pattern di puntatori e lifetime insicuri.
Rust incorpora regole chiave nel linguaggio e nel sistema di tipi in modo che il compilatore possa rifiutare per default intere categorie di bug, pur consentendo delle uscite esplicite quando necessario.
Il GC può introdurre overhead a runtime e, soprattutto in alcuni carichi di lavoro di sistema, latenza meno prevedibile (es. pause o lavoro di raccolta in momenti inopportuni).
In domini come i browser, controller real-time o servizi a bassa latenza, conta il comportamento nel peggior caso; perciò Rust punta alla sicurezza mantenendo caratteristiche di performance più prevedibili.
Ownership significa che ogni valore ha un unico “responsabile” (il proprietario). Quando il proprietario esce dallo scope, il valore viene pulito automaticamente.
Questo rende la pulizia prevedibile e previene situazioni in cui due parti pensano entrambe di dover liberare la stessa allocazione.
Un move trasferisce la proprietà da una variabile a un'altra; la variabile originale non può più usare il valore dopo il move.
Questo evita i casi di “due proprietari per la stessa allocazione”, che spesso portano a double-free o use-after-free nei linguaggi con gestione manuale della memoria.
Il borrowing permette al codice di usare temporaneamente un valore tramite riferimenti senza prenderne la proprietà.
La regola centrale è: molti lettori o un solo scrittore—puoi avere molteplici riferimenti condivisi (&T) oppure un riferimento mutabile (&mut T), ma non entrambi contemporaneamente. Questo previene molte classi di bug dovuti a mutazioni durante la lettura e aliasing pericolosi.
Un lifetime è “per quanto tempo un riferimento è valido”. Rust richiede che i riferimenti non sopravvivano ai dati a cui puntano.
Il borrow checker applica questo al tempo di compilazione, quindi il codice che potrebbe produrre riferimenti pendenti viene rifiutato prima di essere eseguito.
Una data race si verifica quando più thread accedono allo stesso indirizzo di memoria contemporaneamente, almeno uno scrive e non esiste coordinazione.
Le regole di ownership/borrowing si estendono alla concorrenza: pattern di condivisione insicuri sono difficili o impossibili da esprimere in codice sicuro, spingendo verso primitive di sincronizzazione esplicite o message passing.
Gran parte del codice è scritto in safe Rust, dove il compilatore impone regole che prevengono i bug di memoria. unsafe è un’uscita chiaramente marcata per operazioni che il compilatore non può provare sicure (es. alcune chiamate FFI o primitive di basso livello).
Una pratica comune è mantenere l’unsafe piccolo e incapsulato dietro API sicure, in modo che sia più facile da controllare in revisione del codice.