Rust (linguaggio di programmazione)

Da Wikipedia, l'enciclopedia libera.
Rust
Rust programming language black logo.svg
Autore Inizialmente Graydon Hoare, poi i Rust Project Developers, finanziati dalla Mozilla Foundation
Data di origine 2010
Ultima versione 1.9 / 26 maggio 2016
Utilizzo general-purpose
Paradigmi compilato, imperativo, strutturato, funzionale, object-oriented
Tipizzazione statica, forte, inferita, nominale, lineare
Estensioni comuni .rs, .rlib
Influenzato da Alef, C#, C++, Cyclone, Erlang, Haskell, Hermes, Limbo (linguaggio), Newsqueak, NIL, OCaml, Ruby, Scheme, Standard ML, Swift
Ha influenzato C# 7, Elm, Idris, Swift
Implementazione di riferimento
Implementazione www.rust-lang.org
Sistema operativo Windows, Mac OS, Linux
Licenza MIT / Apache 2
Sito web www.rust-lang.org

Rust è un linguaggio di programmazione compilato, multi-paradigma, ad uso generico, sviluppato da Mozilla Research, in collaborazione con la comunità open-source. Ha l'obiettivo di essere un linguaggio efficiente, sicuro, e idoneo a sviluppare software di sistema concorrente, ed è progettato per supportare i paradigmi di programmazione imperativo-procedurale, funzionale, e object-oriented.

Il linguaggio è emerso da un progetto personale del dipendente di Mozilla Graydon Hoare. L'appoggio 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. Tale compilatore, noto come rustc, è riuscito a compilare sé stesso nel 2011. Come back end usa il framework open source LLVM.

Il primo rilascio del compilatore di Rust è avvenuto nel gennaio del 2012. Il rilascio della prima versione stabile, la 1.0, è avvenuto il 15 maggio 2015.

Sebbene il suo 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 del 2016 effettuato tra gli sviluppatori di Stack Overflow, Rust si è classificato al primo posto come "Most Loved Programming Language"[1] (in italiano, linguaggio di programmazione più amato).

Si ritiene che il linguaggio prenda il suo nome dal nome inglese dai ruggini, una specie di funghi che attacca le foglie delle piante[2].

Progettazione[modifica | modifica wikitesto]

L'obiettivo di Rust è di essere un buon linguaggio per creare sistemi complessi altamente sicuri, anche nel caso siano multithreded. 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 occupazione di memoria, throughput e latenza confrontabili a un analogo tipico programma scritto in C. Anche i tempi di compilazione sono confrontabili.

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; inoltre, nonostante l'assenza della garbage collection, non è possibile avere memory leak.

Riguardo alla sicurezza della concorrenza, il linguaggio impedisce operazioni concorrenti dall'esito indefinito, come accesso l'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. Tale ereditarietà può essere sia statica (analoga ai trait dei template del C++), che 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, tali 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à" ("ownership") degli oggetti, e di "prestito" ("borrowing") degli oggetti, per decidere quali istruzioni possono leggere oggetto e quali lo possono scrivere;
  • si consente (e talvolta si richiede) che il programmatore specifichi un "tempo di vita" ("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'ereditarità di implementazione, cioè ogni classe può ereditare solo da interfacce, non da altre classi;
  • le classi non sono oggetti (non esistono metaclassi);
  • le classi non hanno 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 la reflection, né la valutazione di espressioni in fase di esecuzione (eval).

Storia[modifica | modifica wikitesto]

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, ~ and @, il che semplificava il modello di memoria interno. Il primo di tali tipi di puntatori è stato sostituito dalla funziona 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 prima versione stabile, la 1.0, è stato promesso che le successive versioni 1.x sarebbero state compatibili con essa.

Esempi[modifica | modifica wikitesto]

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".
// 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 {
        risultato *= i;
        i += 1;
    }
    // Ritorno esplicito, diversamente dalla funzione precedente.
    return risultato;
}

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

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
// uno strato di indirettezza, 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)
        }
    }
}

External links[modifica | modifica wikitesto]

Informatica Portale Informatica: accedi alle voci di Wikipedia che trattano di informatica
  1. ^ Stack Overflow Developer Survey 2016 Results, su Stack Overflow. URL consultato il 17 giugno 2016.
  2. ^ Internet archaeology: the definitive, end-all source for why Rust is named "Rust" • /r/rust, su reddit. URL consultato il 17 giugno 2016.