Rust - Introduzione


Rust è un linguaggio di programmazione general purpose pensato come alternativa alla consolidata coppia C/C++ (e ad altri attori in questo scenario, alcuni dei quali consolidati o emergenti come D, Nim e Zig, magari anche Go, sia pure con caratteristiche e target leggermente diversi) per la programmazione di basso livello e di sistema, inserendosi da protagonista in un'arena decisamente importante. Esso può essere anche proficuamente utilizzato per lo sviluppo di giochi e per la programmazione Web, ad esempio vedremo come si tratti di uno strumento perfettamente idoneo per lavorare con il ben noto standard conosciuto con il nome Webassembly. Da buon linguaggio moderno si propone molto bene anche come strumento per la programmazione concorrente. Insomma, possiamo adottarlo in ambito veramente e completamente general purpose, come detto.
In questa sede  non starò a ripercorrere la lunga (e un po' tediosa) storia della programmazione che potrete trovare ovunque sul Web e in molti libri che trattano di linguaggi (e che con queste vicende aggiungono spesso 20-30 pagine al totale dell'opera).
Viceversa, vediamo un breve excursus, del tutto generico, sulle caratteristiche principali del linguaggio, senza perdere troppo tempo, avrete modo di apprezzare il tutto nel corso del nostro cammino.
Rust è un linguaggio compilato, come è normale che sia stante le finalità dichiarate, che gira sulle solite diffuse piattaforme, Linux, Windows, BSD e Mac ma anche numerosi target embedded (ARM-Cortex, ad esempio). E sotto il cofano c'è la ben nota ed apprezzatissima infrastruttura LLVM, punto di riferimento per tanti linguaggi di programmazione.
I paradigmi supportati possono essere individuati come i seguenti:
  • imperativo e procedurale
  • funzionale
  • strutturato
  • orientato agli oggetti, pur non essendo esattamente un linguaggio puro object oriented come Java, Python, Ruby o C#, diciamo, "orientato agli oggetti a modo suo" e vedremo come.
  • event driven anche se va specificato. Il linguaggio core non è event-driven, ma il suo sistema async/await insieme ad executor come Tokio lo rendono un ottimo linguaggio per architetture event-driven. È una caratteristica dell'ecosistema più che del linguaggio in sé.
  • data oriented intendendo più che altro con questo un forte controllo sui dati e la loro manipolazione in memoria
La sicurezza a livello di integrità dei dati e della memoria è perseguita in maniera quasi maniacale, i tipi dei dati vengono verificati già in fase di compilazione il che garantisce che per questo aspetto vi sia una limitazione dei crash a runtime. Va precisato, a scanso di equivoci, che restano possibili gli stati di "panico" (ed esempio a fronte di una divisione per 0) che comportano l'arresto di un programma. Come vedremo, la distinzione che Rust fa è tra errori recuperabili (gestiti con Result) ed errori irrecuperabili (panic). Il sistema dei tipi elimina intere categorie di bug, non la totalità dei comportamenti anomali a runtime.  La tipizzazione dei dati è forte e statica (quindi non sono ammesse pericolose conversioni implicite e, come detto, il tipo di ogni variabile è determinato al momento della compilazione, non durante l'esecuzione) ed è supportata l'inferenza di tipo. La sicurezza della memoria è gestita tramite un complesso e innovativo sistema di regole che rende superflua, tra l'altro, la presenza di un garbage collector. Tra queste regole impareremo a conoscere i concetti di barrowing e ownership la cui natura può risultare molto utile nella scrittura di codice concorrente. L'apprendimento di questi meccanismi insieme a quello di lifetime è di certo il cardine principale del linguaggio. Tutto questo è disponibile senza dover sacrificare gli aspetti prestazionali e a questo proposito nell'ambito di Rust si parla anche di astrazione a costo zero il che appunto significa, in pratica, che costrutti di più alto livello, quando adoperati, non avranno impatto negativo sulle prestazioni. Aggiungiamo alle caratteristiche principali la presenza di un potente sistema di metaprogrammazione. Ciliegina sulla torta, è relativamente semplice (che non vuol dire banale) cooperare con il linguaggio C, con quanto ne consegue in termini di usabilità di codice scritto in quel linguaggio. Una nota di particolare merito va al compilatore che fornisce messaggi di errore spesso molto utili e chiari arrivando a proporre le soluzioni ai problemi riscontrati. In questo ambito è il migliore tra quelli da me utilizzati. Queste sono sostanzialmente le caratteristiche principali del linguaggio, lungi dall'aver sviscerato del tutto l'argomento, ritengo inutile scendere in ulteriori dettagli che intanto qui servirebbero a poco. Normalmente quello che è scritto nell'introduzione (ammesso che questa non venga saltata) viene presto dimenticato.
Può però essere interessante vedere qualche differenza interessante rispetto ad altri comuni linguaggi di programmazione ovvero dove Rust presenta i suoi punti di forza (più avanti vedremo anche qualche debolezza):

Linguaggio Differenze principali rispetto a Rust
C/C++ Rust elimina intere categorie di bug comuni come buffer overflow, use-after-free e data races, garantiti a compile-time anziché affidati alla disciplina del programmatore.
Python Python è dinamico e interpretato, mentre Rust è staticamente tipizzato e compilato, con prestazioni molto superiori.
Java Java usa garbage collection e ha runtime pesante; Rust è più leggero e offre controllo diretto sulla memoria.
Go Go punta sulla semplicità e ha garbage collector; Rust è più complesso da apprendere ma offre maggiore controllo sulla memoria, assenza di GC e performance più prevedibili.
Swift Entrambi hanno gestione sicura della memoria, ma Rust è più rigoroso e orientato alla concorrenza sicura. In generale Rust garantisce la sicurezza a compile-time senza alcun meccanismo a runtime, Swift no (per quanto ne so, ad essere onesti)
Ada Ada è storicamente usato in sistemi critici, (ed è una gran bel linguaggio da quel punto di vista) ma Rust offre una sintassi decisamente più moderna, produttiva e ha una community più attiva.

Nato intorno al 2006 inizialmente grazie a Graydon Hoare e successivamente portato avanti dalla Mozilla Foundation, con la prima release stabile presentata nel maggio del 2015 (fatto interessante, le primissime versioni del compilatore furono scritte in OCaml, mentre divenne self-hosting nel 2011) Rust ha rapidamente scalato le classifiche di gradimento da parte di moltissimi sviluppatori venendo anche adottato da grandi realtà tra le quali spiccano, oltre ovviamente a Mozilla, anche Amazon, Cloudflare, Dropbox, Google, Oracle, Samsung e Facebook. La stessa Microsoft sta pesantemente adottando Rust al suo interno per lo sviluppo dei suoi prodotti e delle sue tecnologie e ne ha tratto ispirazione per il progetto Verona (che probabilmente non evolverà fino al prodotto finale). Molti altri linguaggi in qualche modo ne sono stati influenzati. Anche questo consenso può essere prova della validità di questo linguaggio e del suo design. Intorno ad esso si è creata una comunità ampia di sviluppatori ed una buona base di conoscenza. L'evoluzione del linguaggio procede in maniera continua e ogni release aggiunge interessanti caratteristiche. Il tutto è liberamente fruibile e consultabile su GitHub.

Punti negativi? Vediamone alcuni, saltellando un po' qua un po' là. Al di là dei gusti dei singoli programmatori, cui il linguaggio può più o meno piacere, forse uno solo è condiviso, ovvero la curva di apprendimento, che è generalmente abbastanza ripida. Niente che non si possa domare con un po' di pazienza, volontà e tanta voglia di chiedere ed informarsi, la comunità come detto è ampia e preparata, però insomma, un qualche sforzo bisogna farlo, soprattutto all'inizio. La gestione della memoria può risultare un po' difficile da comprendere di primo acchito costringendo a qualche ragionamento di più, come detto si tratta di un modello innovativo, abbiamo già citato borrowing, ownership e ancora peggio il lifetime, concetti, in particolare quest'ultimo, che non hanno omologhi in altri linguaggi e chi proviene da essi dovrà operare probabilmente un non immediato cambio di mentalità. Altri aspetti del linguaggio possono risultare un po' complessi, trattandosi di un progetto molto ampio. Davvero molto ampio, vale pena ribadirlo e ricordarlo. Anche la sintassi si presenta a volte un po', come dire, particolare, almeno all'inizio dello studio ma comunque se sperate di ritrovare la semplicità espressiva di Python o Ruby, ecco, non è questo il caso. A proposito di Python, non potete contare in Rust su risorse, in particolare in ambito data-science, paragonabili a quelle disponibili per il linguaggio di Guido Van Rossum. E in generale l'ecosistema non è ancora completo e maturo al 100%, anche se questo non deve far pensare che sia carente e certamente è in continua crescita. Se dovete andare dritto per dritto sull'ecosistema Apple, Swift resta la scelta più indicata. Il modello async/await è arrivato relativamente tardi e alcune aree (come gli async trait) hanno richiesto anni per stabilizzarsi, anche se adesso direi che la cosa è ben consolidata. In aggiunta, si vuole che i tempi di compilazione (ma questo aspetto non ci toccherà viste le dimensioni dei nostri "progetti") non siano i migliori del lotto, ed è probabilmente vero. I messaggi di errore del compilatore sono eccezionali, come ho già detto, ma forse un po' verbosi e bisogna farci l'abitudine.

A causa della complessità e dell'ampiezza di questo progetto la stessa esposizione organica e completa di un argomento può risultare complessa, i rimandi tra un capitolo e l'altro sono inevitabili (almeno per me, ma guardando altri testi mi pare che capiti anche ad autori ben più preparati) proprio per l'intreccio che nasce dalle tante possibilità offerte. L'esposizione stessa dell'argomento tra testi diversi è completamente differente, in maniera ancora più vistosa di quanto ho visto per altri linguaggi, sarà una mia impressione, forse. 

Le procedure di installazione e di manutenzione sono, oltre che molto semplici, ben spiegate e facilmente reperibili sul sito ufficiale (per iniziare è sufficiente scaricare il comodo tool rustup) e ritengo sia inutile ripeterle in questo ambito. Nel prosieguo mi limiterò a parlare specificamente del linguaggio, in particolare userò sempre esempi brevi ed incentrati sull'argomento che sto trattando, mentre vi invito, magari basandovi sulle tante innumerevoli fonti disponibili, a prendere confidenza con un altro eccellente strumento chiamato cargo che peraltro utilizzerò con parsimonia e solo quando necessario, preferendo se possibile la semplice strada della compilazione diretta. Ad ogni modo dedicherò un paragrafo in appedice dedicato a questo importantissimo strumento. Oltre, ovviamente, ad utilizzare le tante fonti disponibili per avere più punti di vista di ciascun argomento. Come ho appena detto, mi concentrerò su un percorso di apprendimento molto schematico e basato su esempi semplici, che saranno centrati sull'argomento trattato. E' stato il mio percorso di apprendimento e potrebbe non essere adatto ad altri.

NB: Durante questo percorso mi sono basato su numerose fonti esterne e testi. che sono elencati nella pagina dei ringraziamenti. Alcuni esempi sono stati creati da me, altri modificati, altri presi quasi identici in quanto a mio avviso pienamente rappresentativi dei concetti che stavo presentando. Questo non è un reference o un libro di testo sul linguaggio. E' il mio, personale, percorso di apprendimento, presentato in forma discorsiva ed accessibile per chiunque.