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>.