Rust è un lin­guag­gio di pro­gram­ma­zio­ne di Mozilla. Può essere usato per scrivere strumenti a riga di comando, ap­pli­ca­zio­ni web e programmi di rete. Il lin­guag­gio, che gode di grande po­po­la­ri­tà tra i pro­gram­ma­to­ri, è adatto anche per la pro­gram­ma­zio­ne vicina all’hardware.

In questo tutorial di Rust vi mostriamo le sue ca­rat­te­ri­sti­che più im­por­tan­ti evi­den­zian­do analogie e dif­fe­ren­ze rispetto ad altri linguaggi di uso comune. Vi guideremo at­tra­ver­so l’in­stal­la­zio­ne di Rust e im­pa­re­re­te a scrivere e compilare il codice Rust sul vostro sistema.

Una pa­no­ra­mi­ca del lin­guag­gio di pro­gram­ma­zio­ne Rust

Rust è un lin­guag­gio compilato. Questa ca­rat­te­ri­sti­ca si traduce in elevate pre­sta­zio­ni; allo stesso tempo il lin­guag­gio offre astra­zio­ni so­fi­sti­ca­te che rendono più facile il lavoro del pro­gram­ma­to­re. Un’at­ten­zio­ne par­ti­co­la­re di Rust è rivolta alla sicurezza della memoria. Ciò gli con­fe­ri­sce un par­ti­co­la­re vantaggio rispetto ai linguaggi più vecchi come C e C++.

Come usare Rust sul proprio sistema

Poiché Rust è un software gratuito e open source (FOSS), chiunque può scaricare la sua toolchain e uti­liz­zar­la sul proprio sistema. A dif­fe­ren­za di Python e Ja­va­Script, Rust non è un lin­guag­gio in­ter­pre­ta­to. Al posto di un in­ter­pre­te viene uti­liz­za­to un com­pi­la­to­re, come in C, C++ e Java. In pratica, questo significa che esistono due fasi per l’ese­cu­zio­ne del codice:

  1. La com­pi­la­zio­ne del codice sorgente che produce un file binario ese­gui­bi­le.
  2. L’ese­cu­zio­ne del file binario ri­sul­tan­te.

Nel caso più semplice, entrambi i passaggi sono con­trol­la­ti dalla riga di comando.

Consiglio

In un altro articolo della nostra Digital Guide diamo un’occhiata più da vicino alla dif­fe­ren­za tra com­pi­la­to­re e in­ter­pre­te.

Rust può essere usato per creare non solo file binari ese­gui­bi­li ma anche librerie. Se il codice compilato è un programma di­ret­ta­men­te ese­gui­bi­le, nel codice sorgente deve essere definita una funzione main(). Come in C / C+++, questo serve come punto d’ingresso nell’ese­cu­zio­ne del codice.

In­stal­la­re Rust sul sistema locale

Per uti­liz­za­re Rust, è ne­ces­sa­rio prima di tutto un’in­stal­la­zio­ne locale. Su macOS si usa il gestore di pacchetti Homebrew. Homebrew funziona anche su Linux. Aprite la riga di comando (‘Terminal.App’ sul Mac), copiate la seguente riga di codice nel terminale ed ese­gui­te­la:

brew install rust
N.B.

Per in­stal­la­re Rust su Windows o su un altro sistema senza l’ausilio di Homebrew, usate lo strumento ufficiale Rustup.

Per ve­ri­fi­ca­re che l’in­stal­la­zio­ne di Rust sia andata a buon fine, aprite una nuova finestra sulla riga di comando ed eseguite il seguente codice:

rustc --version

Se Rust è in­stal­la­to cor­ret­ta­men­te sul vostro sistema, vi verrà mostrata la versione del com­pi­la­to­re. Se invece appare un messaggio di errore, riavviate l’in­stal­la­zio­ne.

Compilare il codice Rust

Per compilare il codice Rust è ne­ces­sa­rio un file di codice sorgente Rust. Aprite la riga di comando ed eseguite i seguenti pezzi di codice. Per prima cosa creeremo una cartella per il tutorial di Rust sul desktop e la apriremo:

cd "$HOME/Desktop/"
mkdir rust-tutorial && cd rust-tutorial

Suc­ces­si­va­men­te creiamo il file del codice sorgente di Rust per un semplice esempio di “Hello World”:

cat << EOF > ./rust-tutorial.rs
fn main() {
    println!("Hello World!");
}
EOF
N.B.

I file del codice sorgente di Rust terminano con l’ab­bre­via­zio­ne .rs.

Infine, com­pi­le­re­mo il codice sorgente di Rust ed ese­gui­re­mo il file binario ri­sul­tan­te:

# compilare il codice sorgente di Rust
rustc rust-tutorial.rs
# eseguire il file binario risultante
./rust-tutorial
Consiglio

Uti­liz­za­te il comando rustc rust-tutorial.rs && ./rust-tutorial per combinare i due passaggi. In questo modo è possibile ri­com­pi­la­re ed eseguire il programma sulla riga di comando premendo il tasto freccia in alto seguito da Invio.

Gestire i pacchetti Rust con Cargo

Oltre al lin­guag­gio Rust vero e proprio esistono vari pacchetti esterni. Questi co­sid­det­ti crates possono essere ottenuti dal Rust Package Registry. A questo scopo viene uti­liz­za­to lo strumento Cargo in­stal­la­to insieme a Rust. Il comando cargo viene usato sulla riga di comando e permette di in­stal­la­re pacchetti e crearne di nuovi. Prima di tutto, ve­ri­fi­ca­te che Cargo sia stato in­stal­la­to cor­ret­ta­men­te:

cargo --version

Imparare le basi di Rust

Per imparare a usare Rust vi con­si­glia­mo di provare di­ret­ta­men­te esempi di codice. A tale proposito è possibile uti­liz­za­re il file rust-tutorial.rs già creato. Copiate un campione di codice nel file, com­pi­la­te­lo ed eseguite il file binario ri­sul­tan­te. Per fun­zio­na­re, il codice di esempio deve essere inserito all’interno della funzione main()!

Potete anche usare Rust Play­ground di­ret­ta­men­te nel vostro browser per provare il codice Rust.

Istru­zio­ni e blocchi

Le istru­zio­ni sono blocchi di base del codice in Rust. Un’istru­zio­ne termina con un punto e virgola (;) e, a dif­fe­ren­za di un’espres­sio­ne, non re­sti­tui­sce un valore. Più istru­zio­ni possono essere rag­grup­pa­te in un blocco. I blocchi sono de­li­mi­ta­ti da parentesi graffe ‘{}’, come in C/C++ e Java.

Commenti

I commenti sono una ca­rat­te­ri­sti­ca im­por­tan­te di qualsiasi lin­guag­gio di pro­gram­ma­zio­ne. Vengono uti­liz­za­ti sia per la do­cu­men­ta­zio­ne del codice che per la sua pia­ni­fi­ca­zio­ne prima che il codice stesso venga scritto. Rust usa la stessa sintassi dei commenti di C, C++, Java e Ja­va­Script, pertanto qualsiasi testo dopo una doppia barra viene in­ter­pre­ta­to come un commento e ignorato dal com­pi­la­to­re:

// Commento
// Commento
// che si estende
// su più
// righe.

Variabili e costanti

In Rust usiamo la parola chiave ‘let’ per di­chia­ra­re una variabile. Una variabile esistente può essere di­chia­ra­ta di nuovo, dopodiché “oscura” la variabile esistente. A dif­fe­ren­za di molti altri linguaggi, il valore di una variabile non può essere cambiato fa­cil­men­te:

// dichiarare variabile ‘età’ e fissare il valore a ‘42’
let età = 42;
// valore della variabile ‘età’ non può essere modificato
età = 49; // errore di compilazione
// la variabile può essere sovrascritta con un nuovo ‘let’
let età = 49;

Per con­tras­se­gna­re il valore di una variabile come suc­ces­si­va­men­te mo­di­fi­ca­bi­le, Rust si serve della parola chiave ‘mut’. Il valore di una variabile di­chia­ra­ta con ‘mut’ può essere mo­di­fi­ca­to:

let mut peso = 78;
peso = 75;

Con la parola chiave ‘const’ viene generata una costante. Il valore di una costante di Rust deve essere noto al momento della com­pi­la­zio­ne. Anche il tipo deve essere spe­ci­fi­ca­to espli­ci­ta­men­te:

const VERSION: &str = "1.46.0";

Il valore di una costante non può essere mo­di­fi­ca­to; una costante non può nemmeno essere di­chia­ra­ta ‘mut’. Inoltre, una costante non può essere di­chia­ra­ta nuo­va­men­te:

// definire costante
const MAX_NUM: u8 = 255;
MAX_NUM = 0; // errore di compilazione, perché il valore di una costante non è modificabile
const MAX_NUM = 0; // errore di compilazione, perché la costante non può essere dichiarata nuovamente

Concetto di proprietà

Una delle ca­rat­te­ri­sti­che decisive di Rust è il concetto di proprietà (in inglese: “ownership”). La proprietà è stret­ta­men­te legata al valore delle variabili, alla loro durata di vita e alla gestione della memoria degli oggetti in memoria dello heap. Quando una variabile si allontana dal campo di ap­pli­ca­zio­ne (in inglese: “scope”), il suo valore viene distrutto e la memoria ri­la­scia­ta. Rust può quindi fare a meno di una “garbage col­lec­tion”, di con­se­guen­za può garantire pre­sta­zio­ni elevate.

Ogni valore ap­par­tie­ne a una variabile, cioè al pro­prie­ta­rio. Ci può essere un solo pro­prie­ta­rio per ciascun valore. Se il pro­prie­ta­rio passa ad altri il valore, allora non è più il pro­prie­ta­rio:

let nome = String::from("Elena Rossi");
let _nome = nome;
println!("{}, world!", nome); // errore di compilazione, perché il valore è passato da ‘nome’ a ‘_nome’

Occorre prestare par­ti­co­la­re at­ten­zio­ne nella de­fi­ni­zio­ne delle funzioni: se una variabile viene passata a una funzione, il pro­prie­ta­rio del valore cambia. La variabile non può essere riu­ti­liz­za­ta dopo l’apertura della funzione. Qui, Rust usa un trucco: invece di passare il valore alla funzione stessa, viene di­chia­ra­to un ri­fe­ri­men­to con il simbolo della e com­mer­cia­le (&). Ciò consente di “prendere in prestito” il valore di una variabile. Di seguito un esempio:

let nome = String::from("Elena Rossi");
// se il tipo del parametro del ‘nome’ viene definito ‘String’ invece di ‘&String’
// la variabile ‘nome’ non può più essere utilizzata dopo l’apertura della funzione
fn ciao(nome: &String) {
    println!("Ciao, {}", nome);
}
// anche l’argomento di funzione deve essere
// contrassegnato come riferimento con ‘&’
ciao(&nome);
// senza l’utilizzo del riferimento questa riga porta a un errore di compilazione
println!("Ciao, {}", nome);

Strutture di controllo

Una proprietà fon­da­men­ta­le della pro­gram­ma­zio­ne è quella di rendere il flusso del programma non lineare. Un programma può diramarsi e i com­po­nen­ti del programma possono essere eseguiti più di una volta. È solo at­tra­ver­so questa va­ria­bi­li­tà che un programma diventa veramente utile.

Rust ha le strutture di controllo di­spo­ni­bi­li nella maggior parte dei linguaggi di pro­gram­ma­zio­ne, tra cui i costrutti dei cicli ‘for’ e ‘while’, così come le ra­mi­fi­ca­zio­ni tramite ‘if’ ed ‘else’. Oltre a ciò, Rust ha alcune ca­rat­te­ri­sti­che speciali. Il costrutto ‘match’ permette l’as­se­gna­zio­ne di pattern, mentre l’istru­zio­ne ‘loop’ crea un ciclo infinito. Per per­met­te­re quest’ultimo, viene uti­liz­za­ta un’istru­zio­ne ‘break’.

Cicli

L’ese­cu­zio­ne ripetuta di un blocco di codice per mezzo di cicli (indicati in inglese come “loop”) è anche nota come “ite­ra­zio­ne”. Spesso l’ite­ra­zio­ne viene eseguita sugli elementi di un con­te­ni­to­re. Come Python, Rust conosce il concetto di “iteratore”. Un iteratore estrae l’accesso suc­ces­si­vo agli elementi di un con­te­ni­to­re. Facciamo un esempio:

// elenco di nomi
let nomi = ["Elisa", "Giorgio", "Francesca"];
// ciclo ‘for’ con iteratore nell’elenco
for nome in nomi.iter() {
    println!("Ciao, {}", nome);
}

Ora, cosa fare se si vuole scrivere un ciclo ‘for’ nello stile C/C++ o Java? Volete dunque spe­ci­fi­ca­re un numero iniziale e un numero finale e passare in rassegna tutti i valori intermedi. In tal caso, esiste l’oggetto “range” in Rust, proprio come in Python. Questo a sua volta crea un iteratore su cui opera la parola chiave ‘for’:

// emettere numeri da ‘1’ a ‘10’
// ciclo ‘for’ con iteratore ‘range’
// attenzione: range non contiene il numero finale!
for numero in 1..11 {
    println!("Numero: {}", numero);
}
// (inclusa) scrittura range alternativa
for numero in 1..=10 {
    println!("Numero: {}", numero);
}

Un ciclo ‘while’ funziona in Rust come nella maggior parte degli altri linguaggi. Viene impostata una con­di­zio­ne e il corpo del ciclo viene eseguito finché la con­di­zio­ne è vera:

// emettere i numeri da ‘1’ a ‘10’ tramite ciclo ‘while’
let mut numero = 1;
while (numero <= 10) {
    println!(Numero: {}, numero);
    numero += 1;
}

Per tutti i linguaggi di pro­gram­ma­zio­ne è possibile creare un ciclo infinito con ‘while’. Nor­mal­men­te si tratta di un errore, ma ci sono anche casi d’uso che lo ri­chie­do­no. Rust ricorre all’istru­zio­ne “loop” per questi casi:

// ciclo infinito con ‘while’
while true {
    // …
}
// ciclo infinito con ‘loop’
loop {
    // …
}

In entrambi i casi può essere uti­liz­za­ta la parola chiave ‘break’ per uscire dal ciclo.

Ra­mi­fi­ca­zio­ni

Anche la ra­mi­fi­ca­zio­ne con ‘if’ ed ‘else’ funziona in Rust esat­ta­men­te come in altri linguaggi simili:

const limit: u8 = 42;
let numero = 43;
if numero < limit {
    println!("Sotto al limite.");
}
else if numero == limit {
    println!("Proprio al limite…");
}
else {
    println!("Oltre il limite!");
}

Par­ti­co­lar­men­te in­te­res­san­te è la parola chiave ‘match’, che ha una funzione simile all’istru­zio­ne ‘switch’ di altri linguaggi. Per capire come viene uti­liz­za­ta, con­sul­ta­te la funzione simbolo_ carte () nella sezione “Tipi di dati compositi” (vedi sotto).

Funzioni, procedure e metodi

Nella maggior parte dei linguaggi di pro­gram­ma­zio­ne le funzioni sono l’elemento base della pro­gram­ma­zio­ne modulare. Le funzioni sono definite in Rust con la parola chiave ‘fn’. Non viene fatta una di­stin­zio­ne rigorosa tra i relativi concetti di funzione e procedura. Entrambi sono definiti in modi quasi identici.

Una funzione in senso stretto re­sti­tui­sce un valore. Come molti altri linguaggi di pro­gram­ma­zio­ne, anche Rust conosce le procedure, cioè le funzioni che non re­sti­tui­sco­no alcun valore. L’unica re­stri­zio­ne fissa è che il tipo di ritorno di una funzione deve essere espli­ci­ta­men­te spe­ci­fi­ca­to. Se non viene spe­ci­fi­ca­to alcun tipo di ritorno, la funzione non può re­sti­tui­re un valore; allora viene definita come una procedura.

fn procedura() {
    println!("Questa procedura non restituisce alcun valore.");
}
// negare un numero
// Tipo di ritorno dopo l’operatore ‘->‘
fn nega(numerointero: i8) -> i8 {
    return numerointero * -1;
}

Oltre alle funzioni e alle procedure, Rust conosce anche i metodi noti dalla pro­gram­ma­zio­ne orientata agli oggetti. Un metodo è una funzione legata a una struttura di dati. Come in Python, i metodi in Rust sono definiti con il primo parametro ‘self’. Un metodo viene chiamato secondo il consueto schema oggetto.metodo(). Di seguito un esempio del metodo su­per­fi­cie(), legato a una struttura dati ‘struct’:

// definizione ‘struct’
struct rettangolo {
    larghezza: u32,
    altezza: u32,
}
// implementazione ‘struct’
impl rettangolo {
    fn superficie(&self) -> u32 {
        return self.larghezza* self.altezza;
    }
}
let rettangolo = rettangolo {
    larghezza: 30,
    altezza: 50,
};
println!("La superficie del rettangolo è di {}.", rettangolo.superficie());

Tipi di dati e strutture di dati

Rust è un lin­guag­gio tipizzato sta­ti­ca­men­te. A dif­fe­ren­za dei linguaggi dinamici Python, Ruby, PHP e Ja­va­Script, Rust richiede che il tipo di ogni variabile sia noto al momento della com­pi­la­zio­ne.

Tipi di dati ele­men­ta­ri

Come la maggior parte dei linguaggi di pro­gram­ma­zio­ne superiori, Rust conosce alcuni tipi di dati ele­men­ta­ri (“pri­mi­ti­ves”). Le istanze dei tipi di dati ele­men­ta­ri sono assegnate sulla memoria dello stack, che è par­ti­co­lar­men­te per­for­man­te. Inoltre, i valori dei tipi di dati ele­men­ta­ri possono essere definiti con la sintassi “literal”. Ciò significa che i valori possono essere sem­pli­ce­men­te scritti.

Tipo di dato Spie­ga­zio­ne An­no­ta­zio­ne del tipo
Integer Numero intero i8, u8, ecc.
Floating point Numero in virgola mobile f64, f32
Boolean Valore di verità bool
Character Singolo carattere Unicode char
String Catena di caratteri Unicode str

Sebbene Rust sia un lin­guag­gio tipizzato sta­ti­ca­men­te, non sempre il tipo di valore deve essere di­chia­ra­to espli­ci­ta­men­te. In molti casi, il tipo può essere derivato dal com­pi­la­to­re nel contesto (“type in­fe­ren­cer”). In al­ter­na­ti­va, il tipo è espli­ci­ta­men­te spe­ci­fi­ca­to dall’an­no­ta­zio­ne del tipo. In alcuni casi, quest’ultimo è ob­bli­ga­to­rio:

  • Il tipo di ritorno di una funzione deve essere sempre spe­ci­fi­ca­to espli­ci­ta­men­te.
  • Il tipo di costante deve essere sempre spe­ci­fi­ca­to espli­ci­ta­men­te.
  • Le stringhe literal devono essere trattate in modo speciale in modo che la loro di­men­sio­ne sia nota al momento della com­pi­la­zio­ne.

Di seguito alcuni esempi il­lu­stra­ti­vi di istan­zia­zio­ne di tipi di dati ele­men­ta­ri con sintassi literal:

// qui il compilatore riconosce automaticamente il tipo di variabile
let cents = 42;
// annotazione del tipo: numero positivo (‘u8’ = "unsigned, 8 bits")
let età: u8 = -15; // errore di compilazione, perché il valore inserito è negativo
// numero in virgola mobile
let angolo = 38.5;
// equivalente a
let angolo: f64 = 38.5;
// valore di verità
let utente_registrato = true;
// equivalente a
let utente_registrato: bool = true;
// lettera richiede virgolette apici
let lettera = ‘a’;
// catena di caratteri statica richiede virgolette alte
let nome = "Serena";
// con tipo esplicito
let nome: &'static str = "Serena";
// in alternativa come ‘String’ dinamico con ‘String::from()’
let nome: String = String::from("Serena");

Tipi di dati compositi

I tipi di dati ele­men­ta­ri mappano i singoli valori, mentre i tipi di dati compositi rag­grup­pa­no diversi valori. Rust fornisce al pro­gram­ma­to­re una manciata di tipi di dati compositi.

Le istanze dei tipi di dati compositi sono assegnate sullo stack come le istanze dei tipi di dati ele­men­ta­ri. Per poterlo fare, le istanze devono avere una di­men­sio­ne fissa. Ciò significa anche che non possono essere mo­di­fi­ca­ti ar­bi­tra­ria­men­te dopo l’istan­zia­zio­ne. Di seguito un riepilogo dei più im­por­tan­ti tipi di dati compositi in Rust:

Tipo di dato Spie­ga­zio­ne Tipo di elemento Sintassi literal
Array Elenco di più valori Stesso tipo [a1, a2, a3]
Tuple Di­spo­si­zio­ne di più valori Qualsiasi tipo (t1, t2)
Struct Rag­grup­pa­men­to di più valori nominati Qualsiasi tipo
Enum Enu­me­ra­zio­ne Qualsiasi tipo

Vediamo ora da vicino una struttura di dati ‘struct’. Definiamo una persona con tre campi nominati:

struct persona = {
    nome: String,
    cognome: String,
    età: u8,
}

Per rap­pre­sen­ta­re una persona concreta, istan­zia­mo ‘struct’:

let giocatore = persona {
    nome: String::from("Elena"),
    cognome: String::from("Rossi"),
    età: 42,
};
// accedere al campo di un’istanza ‘struct’
println!("L’età del giocatore è: {}", giocatore.età);

Una ‘enum’ (ab­bre­via­zio­ne di “enu­me­ra­tion”, enu­me­ra­zio­ne) rap­pre­sen­ta le possibili varianti di una proprietà. Lo il­lu­stria­mo qui con un esempio dei quattro possibili semi di una carta da gioco:

enum semecarta {
    fiori,
    picche,
    cuori,
    quadri,
}
// il seme di una carta da gioco concreta
let seme = semecarta::fiori;

Rust conosce la parola chiave ‘match’ per “pattern matching”. La fun­zio­na­li­tà è pa­ra­go­na­bi­le all’istru­zio­ne ‘switch’ di altri linguaggi. Di seguito un esempio:

// determinare il simbolo appartenente al seme di una carta
fn simbolo_carta(seme: semecarta) -> &'static str {
    match seme {
        semecarta::fiori => "♣︎",
        semecarta::picche => "♠︎",
        semecarta::cuori => "♥︎",
        semecarta::quadri => "♦︎",
    }
}
println!("simbolo: {}", simbolo_carta(semecarta::fiori)); // restituisce il simbolo ♣︎

Una tupla è una di­spo­si­zio­ne di diversi valori, i quali possono essere di tipo diverso. I singoli valori della tupla possono essere assegnati a diverse variabili at­tra­ver­so la de­strut­tu­ra­zio­ne. Se uno dei valori non è ne­ces­sa­rio, l’un­der­sco­re (_) viene uti­liz­za­to come se­gna­po­sto, come di consueto in Haskell, Python e Ja­va­Script. Di seguito un esempio:

// definire carta da gioco come tupla
let carta: (semecarta, u8) = (semecarta::cuori, 7);
// assegnare i valori di una tupla a più variabili
let (seme, valore) = carta;
// dovessimo avere bisogno solo del valore
let (_, valore) = carta;

Poiché i valori delle tuple sono ordinati, è possibile accedervi anche tramite un indice numerico. L’in­di­ciz­za­zio­ne non avviene tra parentesi quadre, ma uti­liz­zan­do una sintassi a punti. Nella maggior parte dei casi, la de­strut­tu­ra­zio­ne dovrebbe portare a una maggiore leg­gi­bi­li­tà del codice:

let nome = ("Elena", "Rossi");
let cognome = nome.0;
let cognome = nome.1;

Ap­pren­de­re i costrutti di pro­gram­ma­zio­ne in Rust

Strutture dati dinamiche

I tipi di dati compositi già in­tro­dot­ti hanno in comune il fatto che le loro istanze sono assegnate sullo stack. La libreria standard di Rust contiene anche una serie di strutture di dati dinamici di uso comune. Le istanze di queste strutture dati sono assegnate sullo heap. Ciò significa che la di­men­sio­ne delle istanze può essere mo­di­fi­ca­ta in seguito. Di seguito un breve riepilogo delle strutture dati dinamiche usate fre­quen­te­men­te:

Tipo di dati Spie­ga­zio­ne
Vector Elenco dinamico di più valori dello stesso tipo
String Suc­ces­sio­ne dinamica di lettere Unicode
HashMap As­se­gna­zio­ne dinamica di coppie di valori chiave

Di seguito un esempio di un vettore dinamico crescente:

// dichiarare vettore con ‘mut’ come modificabile
let mut nome = Vec::new();
// aggiungere valori al vettore
nomi.push("Elisa");
nomi.push("Giorgio");
nomi.push("Francesca");

Pro­gram­ma­zio­ne orientata agli oggetti (OOP) in Rust

A dif­fe­ren­za di linguaggi come C++ e Java, Rust non conosce il concetto di classi. Tuttavia, è possibile pro­gram­ma­re secondo la me­to­do­lo­gia OOP. La base è co­sti­tui­ta dai tipi di dati già pre­sen­ta­ti. So­prat­tut­to il tipo ‘struct’ può essere usato per definire la struttura degli oggetti.

In più, in Rust esistono i ‘traits’, cioè le ca­rat­te­ri­sti­che. Un trait raggruppa un insieme di metodi che possono poi essere im­ple­men­ta­ti con qualsiasi tipo. Un trait comprende le di­chia­ra­zio­ni di metodo, ma può anche contenere delle im­ple­men­ta­zio­ni. Con­cet­tual­men­te, un trait si colloca a metà strada tra un’in­ter­fac­cia Java e una classe base astratta.

Un trait esistente può essere im­ple­men­ta­to da diversi tipi. Inoltre, un tipo può im­ple­men­ta­re diversi traits. Rust permette quindi la com­po­si­zio­ne di fun­zio­na­li­tà per diversi tipi senza dover ereditare da un’in­ter­fac­cia comune.

Me­ta­pro­gram­ma­zio­ne

Come molti altri linguaggi di pro­gram­ma­zio­ne, Rust permette di scrivere codice per la me­ta­pro­gram­ma­zio­ne. Si tratta di codice che genera ulteriore codice. In Rust questo include da un lato le “macro” che potreste conoscere da C/C++. Le macro terminano con un punto escla­ma­ti­vo (!); la macro ‘println!’ per l’output di testo sulla riga di comando è già stata men­zio­na­ta più volte in questo articolo.

D’altra parte, Rust conosce anche i “generics”, i quali per­met­to­no di scrivere codice astrai­bi­le in diversi tipi. I generics sono all’incirca pa­ra­go­na­bi­li ai modelli in C++ o agli omonimi generics in Java. Un generic spesso usato in Rust è ‘Option<T>‘, che astrae la dualità ‘None’/’Some(T)’ per qualsiasi tipo ‘T’.

In sintesi

Rust ha il po­ten­zia­le per so­sti­tui­re i favoriti di lunga data C e C++ come lin­guag­gio di pro­gram­ma­zio­ne di sistema.

Vai al menu prin­ci­pa­le