Rust (linguaggio di programmazione)

Rust
linguaggio di programmazione
AutoreInizialmente Graydon Hoare, poi i Rust Project Developers, finanziati dalla Mozilla Foundation
Data di origine2010
Ultima versione1.82.0 (17 ottobre 2024)
Utilizzosystem programming, general-purpose
Paradigmicompilato, imperativo, strutturato, funzionale, object-oriented
Tipizzazionestatica, forte, inferita, nominale, lineare
Estensioni comuni.rs, .rlib
Influenzato daAlef, C#, C++, Cyclone, Erlang, Haskell, Hermes, Limbo, Newsqueak, NIL, OCaml, Ruby, Scheme, Standard ML
Ha influenzatoC# 7, Elm, Idris, Swift, Zig
Implementazione di riferimento
Implementazione[1]
Sistema operativoWindows, Mac OS, Linux
Licenzalicenza Apache 2.0 e licenza MIT
Sito webwww.rust-lang.org/

Rust è un linguaggio di programmazione compilato, multi-paradigma, ad uso generale, sviluppato da Mozilla Research, in collaborazione con la comunità open-source. Si pone l'obiettivo di essere un linguaggio efficiente, sicuro, e idoneo a sviluppare software di sistema concorrente. È progettato per permettere i paradigmi di programmazione imperativo, procedurale, funzionale, e orientata agli oggetti.

Il linguaggio è emerso da un progetto personale di un dipendente di Mozilla, Graydon Hoare. Il sostegno al progetto da parte di Mozilla è cominciato nel 2009 ed è stato annunciato nel 2010. Lo stesso anno è iniziata la riscrittura in Rust del compilatore stesso, inizialmente scritto in OCaml. Questo compilatore, noto come rustc, è riuscito a compilare sé stesso nel 2011. Come back end usa il framework open source LLVM.

La prima versione del compilatore di Rust è stata pubblicata nel gennaio del 2012. La prima versione stabile, la 1.0, è uscita il 15 maggio 2015.

Sebbene lo sviluppo sia sostenuto da Mozilla, si tratta di un progetto aperto alla comunità open-source, che contribuisce attivamente. La progettazione del linguaggio è stata raffinata dall'esperienza di utilizzo nello sviluppo del motore di browser Web Servo e del compilatore rustc.

In un sondaggio effettuato nel 2016 tra gli iscritti di Stack Overflow, Rust si è classificato al primo posto come "linguaggio di programmazione più amato" (Most Loved Programming Language).[1] Questa posizione è stata confermata anche nei sondaggi del 2017[2], del 2018[3], del 2019[4], del 2020[5], del 2021[6] e del 2022.[7]

Si ritiene che il linguaggio prenda il nome dal nome inglese delle ruggini, specie di funghi che attaccano le foglie delle piante.[8]

Progettazione

L'obiettivo di Rust è di essere un buon linguaggio per creare sistemi complessi altamente sicuri, anche nel caso siano multi threaded. Ciò ha condotto a un insieme di caratteristiche che enfatizzano le prestazioni, il controllo dell'allocazione di memoria, la sicurezza e la concorrenza.

Riguardo alle prestazioni, ogni tipico programma scritto in Rust ha una maggiore sicurezza sulla scrittura in memoria rispetto al C++ cercando di mantenere la stessa velocità di quest'ultimo per la maggior parte delle applicazioni[9]. I tempi di compilazione sono però più lunghi.

Riguardo all'allocazione di memoria, ogni tipo usa un numero di bit definito dal linguaggio e ha un allineamento definibile dal programmatore, inoltre non viene usata la garbage collection, consentendo una deallocazione deterministica della memoria.

Riguardo alla sicurezza, il linguaggio vieta costrutti dal comportamento indefinito, come la lettura di variabili non ancora scritte, l'accesso oltre i limiti di array, la dereferenziazione di puntatori nulli o non validi, l'uso di iteratori invalidati.

Nonostante l'assenza di garbage collection, la memoria allocata dinamicamente viene deallocata automaticamente grazie al fatto che il compilatore inserisce le opportune istruzioni quando le variabili escono dal proprio ambito di visibilità. I memory leak sono considerati sicuri, ma si possono verificare solo in particolari situazioni: ad esempio chiamando apposite funzioni di libreria (Box::leak, std::mem::forget, ...) o creando cicli di puntatori reference counted (Rc, Arc).

Riguardo alla sicurezza della concorrenza, il linguaggio impedisce operazioni concorrenti dall'esito indefinito, come l'accesso simultaneo in scrittura alla stessa variabile.

La sintassi di Rust somiglia a quella del linguaggio C per l'uso delle graffe per racchiudere strutture o blocchi di istruzioni, per gli operatori aritmetici e logici, e per le parole chiave di controllo del flusso if, else, for, while, break, continue, e return. I tipi primitivi di Rust sono analoghi ai tipi del C, anche se con nomi quasi tutti diversi. Tuttavia la semantica di Rust è parecchio diversa da quelle di C e C++.

Il sistema dei tipi supporta un costrutto detto trait, ispirato dal linguaggio Haskell, che consente l'ereditarietà multipla di interfacce, ma non l'ereditarietà di implementazione. Questa ereditarietà può essere sia statica, analoga ai trait dei template del C++, sia dinamica, analoga alle funzioni virtuali del C++.

Rust è dotato di inferenza di tipo, cioè ogni volta che si dichiara una variabile locale, è facoltativo dichiararne il tipo. Quest'ultimo viene desunto dall'espressione di inizializzazione o dalle successive istruzioni di assegnamento a tale variabile.

Non è obbligatorio inizializzare le variabili. Tuttavia viene generato errore di compilazione se una variabile viene usata prima di ricevere un valore, o se le vengono assegnati valori di tipi diversi.

Le classi (chiamate struct) e le funzioni possono essere parametrizzate da tipi in modo analogo ai template del C++. Tuttavia questi tipi parametrici devono essere vincolati a dei trait per poterne usare i metodi o gli operatori.

Rust si differenzia da molti altri linguaggi object-oriented per le seguenti scelte progettuali:

  • si usano i concetti di "proprietà" (in inglese ownership) degli oggetti, e di "prestito" (in inglese borrowing) degli oggetti per decidere quali istruzioni possono leggere un oggetto e quali lo possono scrivere;
  • si consente, e talvolta si richiede, che il programmatore specifichi un "tempo di vita" (in inglese lifetime) associato a oggetti gestiti tramite puntatori, per decidere quando tali oggetti devono essere distrutti;
  • non si usa il concetto di eccezione;
  • non si usa l'ereditarietà di implementazione, cioè ogni classe può ereditare solo da interfacce, non da altre classi;
  • le classi non sono oggetti, ovvero non esistono metaclassi;
  • le classi non hanno eredità implicita da una interfaccia comune;
  • non si usa l'overload delle funzioni, cioè nello stesso ambito le funzioni devono avere nomi diversi;
  • non si usa né la riflessione né la valutazione di espressioni in fase di esecuzione (eval).

Storia

Lo stile del sistema ad oggetti è cambiato considerevolmente tra le versioni 0.2, 0.3 e 0.4 di Rust. La version 0.2 ha introdotto le classi. La versione 0.3 ha aggiunto alcune funzionalità tra cui i distruttori e il polimorfismo tramite l'uso delle interfacce. In Rust 0.4, i trait sono stati aggiunti come mezzo per fornire l'ereditarietà, le interfacce sono state unificate nei trait e quindi rimosse come funzionalità distinta. Anche le classi sono state rimosse, sostituite da una combinazione di strutture e di implementazioni.

A partire dalla versione 0.9 e fino alla versione 0.11, Rust aveva due tipi di puntatori, ~ e @, il che semplificava il modello di memoria interno. Il primo di quei tipi di puntatori è stato sostituito dalla funzione di libreria Box e il secondo, che usava la garbage collection, è stato eliminato.

Dato che il motivo principale per cui nessuno adottava Rust era il fatto che il linguaggio ad ogni cambio di versione diventava incompatibile con la versione precedente, all'uscita della 1.0, la prima versione stabile, è stato promesso che le successive versioni 1.x sarebbero state compatibili con essa.

L'8 febbraio 2021 lo sviluppo del linguaggio passa ufficialmente sotto il controllo della Rust Foundation[10], organizzazione non-profit indipendente, finanziata da Mozilla, Microsoft, Google, AWS e Huawei.

Utilizzo

Le caratteristiche di sicurezza, concorrenza e le elevate prestazioni di Rust hanno attirato l'interesse di molte realtà differenti, portando il linguaggio ad essere usato in diversi ambiti.

Di seguito alcuni esempi:

  • Sviluppo web: scrittura di codice WebAssembly; sviluppo di applicazioni web con il framework Rocket.
  • Applicazioni server: Dropbox ha usato Rust per implementare un proprio back-end, separandosi dai servizi Amazon[11]; Cloudflare ha usato Rust per implementare un interprete TCP/IP[12] ad alte prestazioni.
  • Sviluppo di videogiochi: scrittura di motori grafici, come ad esempio Bevy; lo studio di Ready at Dawn usa Rust come linguaggio principale per lo sviluppo di nuovi giochi[13].
  • Sistemi operativi: implementazione di nuovi driver all'interno del kernel Linux[14]; sviluppo di Redox, un SO Unix-like scritto interamente in Rust; Google Fuchsia contiene parti scritte in Rust e supporta il linguaggio per la scrittura di applicazioni native[15].
  • Sviluppo embedded: scrittura e compilazione del codice in formato binario per core ARM Cortex-M[16] ed altri target, sprovvisti del supporto di un sistema operativo.

Esempi

Hello world:

fn main() {
    println!("Hello, world!");
}

Tre versioni della funzione fattoriale, rispettivamente usando gli stili ricorsivo, iterativo, e funzionale:

/* Le diramazioni in questa funzione esibiscono i valori di ritorno
senza usare la parola 'return', che è facoltativa, così da ottenere
uno stile più "funzionale". Notare l'assenza del punto e virgola dopo le 
istruzioni proprio per indicare che quello costituisce il valore di ritorno.
Diversamente dal C e dai linguaggi analoghi, il costrutto 'if' di Rust
è un'espressione invece di un'istruzione, e quindi ha un suo valore
di ritorno, analogamente all'operatore ternario '?:' del C. */
fn fattoriale_ricorsivo(n: u32) -> u32 {
    if n <= 1 {
        1
    } else {
        n * fattoriale_ricorsivo(n - 1)
    }
}

fn fattoriale_iterativo(n: u32) -> u32 {
    /* Le variabili sono dichiarate con la parola 'let'.
     La parola 'mut' consente a queste variabili di venire mutate. */
    let mut i = 1u32;
    let mut risultato = 1u32;
    while i < n {
        i += 1;
        risultato *= i;
    }
    /* Ritorno esplicito, diversamente dalla funzione precedente.
    Questo è considerato "poor-style". La parola chiave return è accettata 
    solo per l'uscita anticipata da una funzione. */
    return risultato;
}

fn fattoriale_funzionale(n: u32) -> u32 {
    /* Gli iteratori hanno vari metodi di trasformazione.
    |accum, x| inizia una funzione anonima (lambda, o "closure" secondo la 
    terminologia usata dalla comunità di Rust).
    Le ottimizzazioni come l'espansione inline rendono questa versione
    tanto efficiente quanto quella iterativa. */
    (1..n).fold(1, |accum, x| accum * (x + 1))
}

fn main() {
    println!("Risultato ricorsivo: {}", fattoriale_ricorsivo(10));
    println!("Risultato iterativo: {}", fattoriale_iterativo(10));
    println!("Risultato funzionale: {}", fattoriale_funzionale(10));
}

Una semplice dimostrazione delle potenzialità di concorrenza di Rust:

use std::thread;

// Questa funzione crea e avvia dieci thread concorrenti.
// Per verificarlo, si esegua il programma più volte e si osservi l'ordine
// irregolare con cui stampa ogni thread.
fn main() {
    // Questa stringa è immutabile,
    // perciò può essere acceduta in sicurezza da più thread.
    let saluto = "Ciao";

    let mut threads = Vec::new();
    // I cicli 'for' operano con qualunque tipo che implementi
    // il trait 'Iterator'.
    for num in 0..10 {
        threads.push(thread::spawn(move || {
            // 'println!' è una macro che verifica staticamente 
            // la stringa di formato. Le macro sono strutturali
            // (come in Scheme), non testuali (come in C).
            println!("{} dal thread numero {}", saluto, num);
        }));
    }

    // Attende la terminazione di ogni thread prima di uscire dal programma.
    for thread in threads {
        thread.join().unwrap();
    }
}

Ecco una dimostrazione degli smart pointer univoci forniti da Rust, insieme alle union taggate e ai metodi:

use ListaInteri::{Nodo, Vuoto};

// Questo programma definisce una struttura dati ricorsiva e implementa
// dei metodi su essa. Le strutture dati ricorsive richiedono
// un livello di indirezione, che qui viene fornito da un puntatore univoco,
// costruito tramite il costruttore 'Box::new'. Questi sono analoghi
// al tipo della libreria standard del C++ 'std::unique_ptr',
// sebbene con maggiori garanzie statiche di sicurezza.
fn main() {
    let lista = ListaInteri::new().prependi(3).prependi(2).prependi(1);
    println!("Somma di tutti i valori della lista: {}.", lista.somma());
    println!("Somma di tutti i valori raddoppiati della lista: {}.",
        lista.moltiplica_per(2).somma());
}

// 'enum' definisce una variante (tagged union), che in fase di esecuzione 
// può valere una di diversi casi.
// Qui un oggetto di tipo 'ListaInteri' o non contiene alcun valore,
// cioè vale 'Vuoto', oppure contiene un numero intero e un puntatore
// a un altro oggetto di tipo 'ListaInteri', cioè vale 'Nodo' con i suoi
// due campi posizionali.
enum ListaInteri {
    Nodo(i32, Box<ListaInteri>),
    Vuoto
}

// Un blocco 'impl' consente di aggiungere definizioni di metodi a un tipo.
// Qui si definiscono per il tipo 'ListaInteri' i metodi
// 'new', 'prependi', 'somma', e 'moltiplica_per'. 
impl ListaInteri {
    fn new() -> Box<ListaInteri> {
        Box::new(Vuoto)
    }

    fn prependi(self, valore: i32) -> Box<ListaInteri> {
        Box::new(Nodo(valore, Box::new(self)))
    }

    fn somma(&self) -> i32 {
        // Le espressioni 'match' sono il modo tipico di effettuare
        // il pattern-matching. Sostituiscono il costrutto 'switch'
        // del linguaggio C, ma sono molto più potenti.
        match *self {
            Nodo(valore, ref prossimo) => valore + prossimo.somma(),
            Vuoto => 0
        }
    }

    fn moltiplica_per(&self, n: i32) -> Box<ListaInteri> {
        match *self {
            Nodo(valore, ref prossimo) =>
                Box::new(Nodo(valore * n, prossimo.moltiplica_per(n))),
            Vuoto => Box::new(Vuoto)
        }
    }
}

Note

  1. ^ Stack Overflow Developer Survey 2016 Results, su Stack Overflow. URL consultato il 17 giugno 2016.
  2. ^ Stack Overflow Developer Survey 2017, in Stack Overflow. URL consultato il 30 maggio 2017.
  3. ^ Stack Overflow Developer Survey 2018, in Stack Overflow. URL consultato il 16 marzo 2018.
  4. ^ Stack Overflow Developer Survey 2019, in Stack Overflow. URL consultato il 12 aprile 2019.
  5. ^ (EN) Ben Popper Director of Content, The 2020 Developer Survey results are here!, su Stack Overflow Blog, 27 maggio 2020. URL consultato il 27 maggio 2020.
  6. ^ (EN) Stack Overflow Developer Survey 2021, su Stack Overflow. URL consultato il 12 dicembre 2021.
  7. ^ (EN) Stack Overflow Developer Survey 2022, su Stack Overflow. URL consultato il 1º luglio 2022.
  8. ^ Internet archaeology: the definitive, end-all source for why Rust is named "Rust", su reddit.com. URL consultato il 17 giugno 2016.
  9. ^ (EN) 0.21 How fast is Rust?, in The Rust Project Developers. URL consultato il 4 maggio 2021.
  10. ^ (EN) Rust Foundation, su foundation.rust-lang.org. URL consultato il 12 febbraio 2021.
  11. ^ (EN) The Epic Story of Dropbox's Exodus From the Amazon Cloud Empire, in Wired. URL consultato il 18 giugno 2021.
  12. ^ (EN) Building fast interpreters in Rust, su The Cloudflare Blog, 4 marzo 2019. URL consultato il 18 giugno 2021.
  13. ^ (EN) Rust for Game Development, su GameFromScratch.com, 31 luglio 2018. URL consultato il 18 giugno 2021.
  14. ^ [PATCH 00/13] [RFC] Rust support, su lore.kernel.org. URL consultato il 18 giugno 2021.
  15. ^ (EN) Rust | Fuchsia, su Fuchsia. URL consultato il 18 giugno 2021.
  16. ^ rust-embedded/wg, Rust Embedded, 18 giugno 2021. URL consultato il 18 giugno 2021.

Voci correlate

Altri progetti

Collegamenti esterni

Controllo di autoritàLCCN (ENsh2018000672 · GND (DE1078438080 · J9U (ENHE987012402011505171
  Portale Informatica: accedi alle voci di Wikipedia che trattano di informatica