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

Trait in Rust

Operiamo con i Trait in Rust. Grazie ad essi il linguaggio permette di associare delle funzionalità a delle struct
Operiamo con i Trait in Rust. Grazie ad essi il linguaggio permette di associare delle funzionalità a delle struct
Link copiato negli appunti

I trait sono il modo con cui Rust associa delle funzionalità a delle struct. Questi ultimi costrutti hanno la peculiare funzionalità di aggregare insieme alcune informazioni per andare a creare un'entità di livello superiore.

Come si definisce un trait in Rust

Istanziando un elemento basato su una struct si ottiene in Rust ciò che di più vicino c'è ad un oggetto di un qualsiasi linguaggio basato sulla Programmazione Orientata agli Oggetti. Ciò che manca sono le funzionalità definite per agire sui dati inseriti in una struct. Quello che - per portare avanti il parallelismo con la OOP - sono i metodi negli oggetti. I trait aggiungono proprio questo: funzioni associabili ad una struct che siano in grado di manipolare i dati inseriti al loro interno.

L'impiego di un trait si articola in tre fasi:

  • definizione della struct. Ciò avverrà nel modo consueto specificando quali sono le informazioni che vogliamo annidare all'interno delle loro istanze;
  • definizione del trait in cui inseriremo principalmente le signature delle singole funzioni;
  • implementazione di un trait e associazione con la struct. Qui andremo a dare davvero un corpo alle funzioni la cui definizione è stata inserita nel trait.

Un esempio pratico

Mettiamo in pratica il tutto con un esempio. Immaginiamo una struct che definisca un generico studente che nel nostro progetto, supponiamo, sarà gestito solo con nominativo e voto:

struct Studente { voto: u8, nome: &'static str }

I valori che verranno incapsulati al suo interno sono una stringa per il nome e un intero senza segno a 8 bit. Definiamo alcune funzionalità che, al momento, sono del tutto scollegate dalla struct:

trait GestioneDati {
    fn crea(nome: &'static str) -> Self;
    fn nome(&self) -> &'static str;
    fn promosso(&self) -> bool;
    fn assegna_voto(&mut self, voto:u8);

Abbiamo definito una funzione crea per la creazione di un nuovo oggetto, una sorta di getter ovvero nome per il recupero del nominativo contenuto nell'oggetto senza alcuna manipolazione. promosso svolge invece il ruolo di property dinamica in quanto valore prodotto all'atto dell'invocazione in base a dati posseduti dall'istanza. assegna_voto permette infine di impostare il valore di una valutazione all'interno dell'istanza.

Mancano le implementazioni delle funzioni ma per il momento notiamo che ogni funzione riceve un parametro self che userà come riferimento all'istanza stessa al fine di accedere ai membri interni della struct.

Implementazione dei metodi

Arriva ora il momento dell'implementazione dei metodi. Nel contempo provvederemo ad associare il trait alla struct che desideriamo usando il costrutto impl...for:

impl GestioneDati for Studente {
    fn crea(nome: &'static str) -> Studente {
        Studente { nome: nome, voto: 0 }
    }
    fn nome(&self) -> &'static str {
        self.nome
    }
    fn promosso(&self) -> bool {
        self.voto>=6
    }
    fn assegna_voto(&mut self, voto:u8) {
        self.voto=voto
    }
}

L'argomento self non viene mai passato al momento dell'invocazione, ciò perché viene passato in maniera del tutto automatica. In pratica, se nel trait abbiamo specificato solo self come argomento di una funzione, questa verrà invocata senza parametri.

Prova dell'implementazione in Rust

E' arrivato il momento di mettere il tutto al lavoro creando un'istanza e osservando il ruolo che le funzionalità descritte e associate hanno. Il main che abbiamo predisposto per il test appare così:

fn main() {
    let mut giovanni: Studente = GestioneDati::crea("Giovanni Neri");
    giovanni.assegna_voto(3);
    if giovanni.promosso() {
              println!("Voto sufficiente, {} è stato promosso", giovanni.nome());
          }
    else {
        println!("Purtoppo {} è stato bocciato", giovanni.nome());
    }
}

Nel codice creiamo una nuova istanza della struct sfruttando il metodo crea. Subito dopo assegniamo il voto che in questo caso non è sufficiente (consideriamo come soglia della sufficienza il 6).

Con if controlliamo se lo studente è stato promosso. In base a questo stamperemo il messaggio che corrisponde a "Purtroppo Giovanni Neri è stato bocciato".

L'aspetto più interessante è che tutte le funzionalità associate alla struct possono essere invocate sulla singola istanza. Proprio come se fossero innestate in essa.

L'utilità dei trait consiste quindi in una sorta di strato operativo che sovrapponiamo alla struct desiderata. Pertanto nulla vieta di associare il medesimo trait a più struct.

Ti consigliamo anche