|
Tra le varie possibilità offerte da questo potente linguaggio ne esiste una che mi ha colpito positivamente ed ' rappresentata dalla keyword impl, e sono certo che anche voi la troverete utile e molto usata nei vari progetti che affronterete. Il suo uso è molto versatile e sostanzialmente riguarda due ambiti: ** implementazione di trait - che per ora non ci interessa ** implementazioni cosiddette inerenti o forse meglio, implementazioni di tipo, che, in pratica, aggiungono funzionalità ad un tipo senza che questo debba implementare altri trait. Si tratta quindi di una potente possibilità di personalizzazione. Il secondo aspetto quindi è quello che ci interessa in questo paragrafo. Questa possibilità come vedremo ci aiuta anche ad avvicinarci al mondo della programmazione a oggetti tipica di altri linguaggi. Rust, come abbiamo detto nell'introduzione, non è un vero e proprio linguaggio object oriented, ma ha alcune caratteristiche che ritroviamo anche in quel popolare paradigma. Un esempio caratteristico che chiarirà il concetto per chi già conosce cosa sia la programmazione a oggetti, coinvolge le struct, partner ideali di impl. Eccolo:
Inizialmente definiamo una normale struttura con due coordinate. Alla riga 5 ecco che entra in funzione la nostra implementazione del tipo struct appena creato. Ad esso aggiungiamo nuove funzionalità (proprio come se fosse un oggetto) che ci permettono di cooperare in maniera più ampia con il nostro Punto. La sintassi come la sua costruzione complessiva è abbastanza semplice tutto sommato: *** impl deve essere seguito dal nome della struttura stessa così da creare un legame con essa; la sintassi base più completa può essere descritta come segue: impl<T> NomeTipo<T> { metodi associati al tipo } *** alla riga 6 troviamo &self che è il solito riferimento immutabile all'istanza corrente. Tale istanza viene creata alla riga 16 l'output è ovvio:
Il discorso è del tutto analogo anche per altri elementi, vediamo ad esempio con un enumerativo con un classico esempio:
Si noti l'uso alla riga 8 dell'operatore di dereferenziazione che serve per accedere ai valori interni di Stato. In alternativa (scomoda) potremmo riscrivere la funzione alla riga 7 come segue: fn descriz(&self) -> &str { match self { &Stato::Attivo => "Attivo", &Stato::Inattivo => "Inattivo", &Stato::Sospeso => "Sospeso", } } Un altro esempio interessante può essere il seguente:
la cui analisi lascio come (semplice) esercizio. E' interessante notare che, come peraltro è normale, possiamo anche usare il tipo generico T nelle implementazioni:
che accetta interi oppure caratteri o anche altro. Un'altra considerazione la merita la clausola where, già incontrata. che viene utilizzata per specificare che un tipo generico deve implementare determinati trait, ad esempio. impl<T> Tipo<T> where T: std::fmt::Display, Come detto impl risulta importante, direi fondamentale, quando si parla dei trait, ma questa trattazione sarà oggetto di altri capitoli. |