Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendice C: Trait Derivabili

In vari punti del libro, abbiamo discusso dell’attributo derive, che si può applicare a una definizione di struct o enum. L’attributo derive genera codice che implementerà un trait con la sua implementazione predefinita sul type annotato con la sintassi derive.

In questa appendice, forniamo un riferimento di tutti i trait nella libreria standard che si possono usare con derive. Ogni sezione copre:

  • Quali operatori e metodi l’implementazione derivata di questo trait abilita
  • Cosa fa l’implementazione del trait fornita da derive
  • Cosa significa per il type implementare questo trait
  • Le condizioni in cui è consentito o non consentito implementare il trait
  • Esempi di operazioni che richiedono il trait

Se si desidera un comportamento diverso da quello fornito dall’attributo derive, consultare la documentazione della libreria standard per ogni trait per dettagli su come implementarli manualmente.

I trait elencati qui sono gli unici definiti dalla libreria standard che possono essere implementati sui vostri type usando derive. Altri trait definiti nella libreria standard non hanno un comportamento predefinito sensato, quindi sta a voi implementarli in modo coerente con gli obiettivi del vostro codice.

Un esempio di trait che non può essere derivato è Display, che gestisce la formattazione per gli utenti finali. Bisogna sempre considerare il modo appropriato per mostrare un type all’utente finale. Quali parti del type dovrebbero essere visibili? Quali parti sarebbero rilevanti? Quale formato dati sarebbe più utile? Il compilatore Rust non ha questa conoscenza, quindi non può fornire un comportamento predefinito adeguato per voi.

Questa lista di trait derivabili non è esaustiva: le librerie possono implementare derive per i propri trait, rendendo la lista di trait che si possono derivare praticamente aperta. L’implementazione di derive coinvolge l’uso di macro procedurali, trattate nella sezione “Macro derive Personalizzate” del Capitolo 20.

Debug per Output da Programmatore

Il trait Debug abilita la formattazione per il debug nelle stringhe di formato, indicata aggiungendo :? all’interno dei segnaposto {}.

Il trait Debug permette di stampare istanze di un type a scopo di debug, così tu e altri programmatori che usano il tuo type potete ispezionare un’istanza in un punto particolare dell’esecuzione di un programma.

Il trait Debug è richiesto, ad esempio, nell’uso della macro assert_eq!. Questa macro stampa i valori delle istanze passate come argomenti se l’asserzione di uguaglianza fallisce, così i programmatori possono vedere perché le due istanze non erano uguali.

PartialEq ed Eq per Confronti di Uguaglianza

Il trait PartialEq permette di confrontare istanze di un type per verificarne l’uguaglianza e abilita l’uso degli operatori == e !=.

Derivare PartialEq implementa il metodo eq. Quando PartialEq è derivato su struct, due istanze sono uguali solo se tutti i campi sono uguali, e sono diverse se anche solo un campo è diverso. Quando è derivato su enum, ogni variante è uguale a se stessa e diversa dalle altre varianti.

Il trait PartialEq è richiesto, ad esempio, dall’uso della macro assert_eq!, che necessita di poter confrontare due istanze per l’uguaglianza.

Il trait Eq non ha metodi. Il suo scopo è segnalare che per ogni valore del type annotato, il valore è uguale a se stesso. Il trait Eq può essere applicato solo a type che implementano anche PartialEq, sebbene non tutti i type che implementano PartialEq possano implementare Eq. Un esempio sono i type numerici float: l’implementazione del float stabilisce che due istanze di valore “non-numerico” (“not-a-number”, NaN) non sono uguali.

Un esempio di quando Eq è richiesto è per le chiavi in una HashMap<K, V>, così che la HashMap<K, V> possa dire se due chiavi sono uguali.

PartialOrd ed Ord per Confronti di Ordinamento

Il trait PartialOrd permette di confrontare istanze di un type a scopo di ordinamento. Un type che implementa PartialOrd può essere usato con gli operatori <, >, <= e >=. Il trait PartialOrd può essere applicato solo a type che implementano anche PartialEq.

Derivare PartialOrd implementa il metodo partial_cmp, che restituisce un Option<Ordering> e restituisce None quando i valori dati non producono un ordinamento valido. Un esempio di valore che non produce ordinamento, sebbene la maggior parte dei valori di quel type possano essere confrontati, è il valore float NaN. Chiamare partial_cmp con qualunque numero float e il valore NaN restituirà None.

Quando derivato su struct, PartialOrd confronta due istanze confrontando i campi in ordine di apparizione nella definizione della struct. Quando derivato su enum, le varianti dichiarate prima sono considerate minori rispetto a quelle definite dopo.

Il trait PartialOrd è richiesto, ad esempio, dalla funzione gen_range del crate rand che genera un valore casuale nell’intervallo specificato da un’espressione range.

Il trait Ord permette di sapere che per qualunque coppia di valori del type annotato, esiste un ordinamento valido. Il trait Ord implementa il metodo cmp, che restituisce un Ordering anziché un Option<Ordering>, poiché un ordinamento valido sarà sempre possibile. Il trait Ord può essere applicato solo a type che implementano anche PartialOrd ed Eq (e Eq richiede PartialEq). Quando derivato su struct ed enum, cmp si comporta allo stesso modo di partial_cmp derivato per PartialOrd.

Un esempio di quando Ord è richiesto è per la memorizzazione di valori in un BTreeSet<T>, una struttura dati che memorizza dati i dati in base all’ordinamento dei valori.

Clone e Copy per Duplicare Valori

Il trait Clone permette di creare esplicitamente una copia profonda di un valore, e il processo di duplicazione può coinvolgere l’esecuzione di codice arbitrario e la copia di dati nell’heap. Consulta la sezione “Interazione tra Variabili e Dati con Clone nel capitolo 4 per ulteriori informazioni.

Derivare Clone implementa il metodo clone, che per il type chiama clone su ciascuna parte del type. Questo significa che tutti i campi o valori del type devono implementare anch’essi Clone per poter derivare Clone.

Un esempio di quando Clone è richiesto è quando si chiama il metodo to_vec su una slice. La slice non possiede le istanze di type che contiene, ma il vettore restituito da to_vec deve possedere le sue istanze, quindi to_vec chiama clone su ogni elemento. Di conseguenza, il type contenuto nella slice deve implementare Clone.

Il trait Copy permette di duplicare un valore copiando solo i dati memorizzati sullo stack; non è necessario eseguire codice arbitrario. Consulta la sezione “Duplicare Dati Sullo Stack del Capitolo 4 per ulteriori informazioni su Copy.

Il trait Copy non definisce alcun metodo, per prevenire che i programmatori ne sovrascrivano i metodi e violino l’assunto che nessun codice arbitrario venga eseguito. In questo modo, tutti possono assumere che copiare un valore sia molto veloce.

Si può derivare Copy su qualunque type in cui tutte le parti implementano Copy. Un type che implementa Copy deve anche implementare Clone, perché un type che implementa Copy avrà già una semplice implementazione di Clone che replicherà quanto fa Copy.

Il trait Copy è raramente richiesto; i type che implementano Copy hanno ottimizzazioni disponibili, il che significa che non bisogna chiamare clone, rendendo il codice più conciso.

Tutto ciò che si può fare con Copy si può anche fare con Clone, ma il codice potrebbe essere più lento o dover usare clone in più punti.

Hash per Mappare un Valore a un Valore di Dimensione Fissa

Il trait Hash permette di prendere un’istanza di un type di dimensione arbitraria e mappare quell’istanza a un valore di dimensione fissa usando una funzione di hash. Derivare Hash implementa il metodo hash. L’implementazione derivata del metodo hash combina il risultato di chiamare hash su ciascuna parte del type, quindi tutti i campi o valori devono anch’essi implementare Hash per poter derivare Hash.

Un esempio di quando Hash è richiesto è per memorizzare chiavi in una HashMap<K, V> per archiviare dati in modo efficiente.

Default per Valori Predefiniti

Il trait Default permette di creare un valore predefinito per un type. Derivare Default implementa la funzione default. L’implementazione derivata della funzione default chiama la funzione default su ogni parte del type, quindi tutti i campi o valori devono anch’essi implementare Default per derivare Default.

La funzione Default::default è comunemente usata in combinazione con la sintassi di aggiornamento delle struct discussa in “Creare Istanze con la Sintassi di Aggiornamento delle Struct nel Capitolo 5. Si possono personalizzare alcuni campi di una struct e poi impostare un valore predefinito per gli altri campi usando ..Default::default().

Il trait Default è richiesto per esempio quando si usa il metodo unwrap_or_default su istanze Option<T>. Se l’Option<T> è None, il metodo unwrap_or_default restituirà il risultato di Default::default per il type T contenuto nell’Option<T>.