Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Rust e struct: progettare i propri tipi di dato

Struct in Rust, un costrutto che permette di aggregare variabili per comporre un nuovo tipo di dato con cui descrivere un'informazione
Struct in Rust, un costrutto che permette di aggregare variabili per comporre un nuovo tipo di dato con cui descrivere un'informazione
Link copiato negli appunti

Prima di parlare di Rust e struct ricordiamo che sinora, nella guida, abbiamo visto molti tipi di dato e strutture che permettono di immagazzinare informazioni nel rispetto della loro natura e organizzarle in vere e proprie reti. Scrivendo i nostri software dovremo procedere ad un lavoro di astrazione per individuare le entità che ne fanno parte e specificarne una "visione informatica" che ne definisca struttura interna e tipi di dato che le compongono. In pratica, dovremo definire strutture informatiche in grado di rappresentare elementi di vita reale di cui il software tratta.

Ad esempio, stiamo scrivendo un gestionale per una biblioteca e dobbiamo creare un tipo di dato in grado di rappresentare un singolo libro. Con le stringhe possiamo definire titolo e autore, con un intero il numero di pagine. Ok ma il libro nel suo complesso come lo definiamo? Ci serve a quel punto inventare un nuovo tipo di dato specifico che andrà a definire l'entità libro. Non in senso generico ma uno specifico tipo di libro adatto al nostro software.

Definire struct in Rust

Per scopi di questo genere utilizziamo le struct, un costrutto che permette di aggregare variabili per comporre un nuovo tipo di dato in grado, tipicamente, di descrivere un'informazione di più alto livello. Nell'esempio precedente, due stringhe ed un intero, con un opportuno significato associato, avrebbero potuto nel complesso identificare l'entità "libro".

Le struct sono connotate da una sintassi piuttosto semplice. Richiedono la parola chiave struct ed una coppia di parentesi graffe. Creiamo la nostra prima "struttura", di nome Esame, che associa due numeri interi che identificano, rispettivamente, il voto conseguito alla prova scritta e quella all'orale:

struct Esame{
    scritto: u8,
    orale: u8
}

La struct Esame dispone di due campi, entrambi di tipo u8 (intero senza segno definito su 8 bit). Possiamo a questo punto creare delle istanze della struct ovvero tanti piccoli "oggetti" ognuno dei quali in grado di definire un esame sostenuto con i voti di scritto e orale. Quanto segue è sufficiente a dare vita ad una singola istanza:

let esame=Esame{scritto:20, orale:24};

Abbiamo inserito i valori 20 e 24, rispettivamente, in scritto e orale. Per poter fare accesso ad ognuna di queste porzioni è sufficiente usare ancora l'operatore punto (.) usando le notazioni esame.scritto e esame.orale:

println!("Voto scritto: {} / voto orale: {}", esame.scritto, esame.orale);

In Rust una struct può essere passata anche ad una funzione come argomento ed utilizzata per gestire funzionalità. Ad esempio, la passiamo ad una funzione in grado di verificare se l'esame è stato superato (supponiamo sia richiesta una media di almeno 18):

fn esame_passato(esame: Esame) -> bool{
  if (esame.orale+esame.scritto)/2>=18 {
     true}
  else {false}
}

e possiamo invocarla in questo modo:

if esame_passato(esame){
     println!("Esame passato");
   }
   else{
     println!("Esame non passato");
   }

Dichiarazione veloce

Quando una struct è estremamente semplice ed i valori sono piuttosto pochi, possiamo definirne una versione più sintetica dove le proprietà sono anonime:

struct Esame(u8,u8);

fn main() {
   let esame=Esame{0:20,1:24};
   println!("Voto scritto: {} / voto orale: {}", esame.0, esame.1);  }
}

In questo caso, abbiamo creato una struct di cui sappiamo solo che ci sono due numeri interi che verranno indirizzati mediante un numero progressivo: esame.0 e esame.1.

Vettori di struct

Dopo aver imparato ad aggregare dati per creare una struttura di livello informativo superiore, proviamo ad usare tali elementi in vettori con la possibilità di avere una sorta di insieme iterativo da esplorare e valutare. Diciamo che i nostri oggetti Esame vengano aggregati in un vettore (struttura dati lineare espandibile già incontrata nella guida) e, uno alla volta con un ciclo, vengano valutati per verificare se la singola prova può essere considerata superata. Il requisito affinché ciò avvenga consiste nel conseguimento della sufficienza in entrambe le prove, sia la scritta sia l'orale. Vediamo un esempio:

struct Esame{
    scritto: u8,
    orale: u8
}
fn superato(esame: &Esame) -> &str {
   if esame.scritto>=18 && esame.orale>=18{
     "Superato"
   }
   else{
     "Non superato"
   }
}
fn main() {
   let mut voti: Vec<Esame> = Vec::new();
   voti.push(Esame{scritto: 10, orale: 14});
   voti.push(Esame{scritto: 28, orale: 22});
   voti.push(Esame{scritto: 30, orale: 26});
   voti.push(Esame{scritto: 21, orale: 12});
   voti.push(Esame{scritto: 27, orale: 28});
   voti.push(Esame{scritto: 24, orale: 18});
   for v in voti.iter(){
       println!("{}-{} Esito: {}", v.scritto, v.orale, superato(v));
   }
}

Compilato ed eseguito offre il seguente output:

10-14 Esito: Non superato
28-22 Esito: Superato
30-26 Esito: Superato
21-12 Esito: Non superato
27-28 Esito: Superato
24-18 Esito: Superato

Come si vede, il vettore è di tipo Vec<Esame> pertanto già impostato per gestire oggetti di tipo Esame. Ogni volta eseguito il ciclo passeremo il riferimento di uno dei suoi elementi alla funzione validazione che non farà altro che restituire una stringa che ci riporta se l'esame si può considerare passato o no.

Ti consigliamo anche