Anexa C: Trăsături derivabile

În diverse părți din carte, am discutat despre atributul derive, pe care poți să-l aplici la o definiție de structură sau enumerare. Atributul derive generează codul care va implementa o trăsătură cu propria implementare implicită pe tipul pe care l-ai adnotat cu sintaxa derive.

În această anexă, oferim o referință a tuturor trăsăturilor din biblioteca standard pe care le poți utiliza cu derive. Fiecare secțiune acoperă:

  • Ce operatori și metode activează derivarea acestei trăsături
  • Ce face implementarea trăsăturii furnizată prin derive
  • Ce semnifică implementarea trăsăturii despre tipul
  • Condițiile în care ți se permite sau nu implementezi trăsătura
  • Exemple de operații care necesită trăsătura

Dacă dorești un comportament diferit de cel oferit de atributul derive, consultă documentația bibliotecii standard pentru fiecare trăsătură, pentru detalii despre cum să le implementezi manual.

Trăsăturile enumerate aici sunt singurele definite de biblioteca standard care pot fi implementate pe tipurile tale folosind derive. Alte trăsături definite în biblioteca standard nu au un comportament implicit bine definit, așa că este de datoria ta să le implementezi în modul care are sens pentru ceea ce încerci să realizezi.

Un exemplu de trăsătură care nu poate fi derivată este Display, care se ocupă de formatarea pentru utilizatorii finali. Ar trebui să iei în considerare întotdeauna modalitatea potrivită de a afișa un tip către un utilizator final. Ce părți ale tipului ar trebui să aibă permisiunea utilizatorilor finali de a vedea? Ce părți ar considera acestea relevante? Ce format al datelor ar fi cel mai relevant pentru ei? Compilatorul Rust nu are aceste cunoștințe, așa că nu îți poate oferi un comportament implicit corespunzător.

Lista de trăsături derivabile furnizate în această anexă nu este exhaustivă: librăriile pot implementa derive pentru propriile lor trăsături, făcând lista de trăsături cu care poți folosi derive cu adevărat deschisă. Implementarea derive implică utilizarea unei macrocomenzi procedurale, ce este acoperită în secțiunea „Macrcomenzi” a Capitolului 19.

Debug pentru afișare de depanare

Trăsătura Debug permite formatarea pentru depanare în string-uri formatate, pe care o indicăm prin adăugarea :? în acoladele {}.

Trăsătura Debug îți permite să printezi instanțe ale unui tip pentru scopuri de depanare, astfel încât tu și alți programatori care utilizează tipul tău să puteți inspecta o instanță la un anumit punct în execuția unui program.

Trăsătura Debug este necesară, de exemplu, în utilizarea macrcomenzii assert_eq!. Această macrocomandă afișează valorile instanțelor date ca argumente dacă aserțiunea de egalitate eșuează, astfel încât programatorii să poată vedea de ce cele două instanțe nu au fost egale.

PartialEq și Eq pentru compararea de egalitate

Trăsătura PartialEq îți permite să compari instanțe ale unui tip pentru a verifica egalitatea și permite utilizarea operatorilor == și !=.

Derivarea PartialEq implementează metoda eq. Când PartialEq este derivat pe structuri, două instanțe sunt egale doar dacă toate câmpurile sunt egale, iar instanțele nu sunt egale dacă orice câmpuri nu sunt egale. Când este derivat pe enumerări, fiecare variantă este egală cu ea însăși și nu este egală cu celelalte variante.

Trăsătura PartialEq este necesară, de exemplu, cu utilizarea macrocomenzii assert_eq!, care are nevoie să poată compara două instanțe ale unui tip pentru egalitate.

Trăsătura Eq nu are metode. Scopul său este de a semnala că pentru fiecare valoare a tipului adnotat, valoarea este egală cu ea însăși. Trăsătura Eq poate fi aplicată doar la tipuri care implementează de asemenea PartialEq, deși nu toate tipurile care implementează PartialEq pot implementa Eq. Un exemplu în acest sens este tipurile cu numere cu virgulă mobilă: implementarea numerelor cu virgulă mobilă afirmă că două instanțe ale valorii care nu este un număr (NaN) nu sunt egale între ele.

Un exemplu de când este necesar Eq este pentru cheile într-un HashMap<K, V> astfel încât HashMap<K, V> să poată spune dacă două chei sunt la fel.

PartialOrd și Ord pentru Compararea Ordinelor

Trăsătura PartialOrd îți permite să compari instanțe ale unui tip în vederea sortării. Un tip care implementează PartialOrd poate fi folosit cu operatorii <, >, <=, și >=. Poți aplica trăsătura PartialOrd doar la tipurile care implementează și PartialEq.

Derivarea lui PartialOrd implementează metoda partial_cmp, care returnează un Option<Ordering> ce va fi None când valorile date nu produc o ordonare. Un exemplu de valoare care nu produce o ordonare, chiar dacă majoritatea valorilor de acel tip pot fi comparate, este valoarea numărului în virgulă mobilă care nu este un număr (NaN). Apelarea partial_cmp cu orice număr în virgulă mobilă și valoarea NaN a unui număr în virgulă mobilă va returna None.

Când este derivată la structuri, PartialOrd compară două instanțe prin compararea valorii în fiecare câmp în ordinea în care câmpurile apar în definiția structurii. Când este derivată la enumerări, variantele enumerării declarate mai devreme în definiția enumerării sunt considerate mai mici decât variantele listate mai târziu.

Trăsătura PartialOrd este necesară, de exemplu, pentru metoda gen_range din crate-ul rand care generează o valoare aleatorie în intervalul specificat de o expresie de interval.

Trăsătura Ord îți permite să știi că, pentru oricare două valori ale tipului adnotat, va exista o ordonare validă. Trăsătura Ord implementează metoda cmp, care returnează un Ordering în loc de un Option<Ordering>, deoarece o ordonare validă va fi întotdeauna posibilă. Poți aplica trăsătura Ord doar la tipurile care implementează și PartialOrd și Eq (iar Eq necesită PartialEq). Când este derivată la structuri și enumerări, cmp se comportă în același mod ca și implementarea derivată pentru partial_cmp cu PartialOrd.

Un exemplu de când Ord e necesar este când stochezi valori într-un BTreeSet<T>, o structură care stochează date bazate pe ordinea de sortare a valorilor.

Clone și Copy pentru duplicarea valorilor

Trăsătura Clone îți permite să creezi explicit o copie profundă a unei valori, iar procesul de duplicare ar putea implica rularea unui cod arbitrar și copierea datelor de pe heap. Vezi secțiunea „Modalități prin care variabilele și datele interacționează: Clone” în Capitolul 4 pentru mai multe informații despre Clone.

Derivarea Clone implementează metoda clone, care atunci când este implementată pentru întregul tip, apelează clone pe fiecare parte a tipului. Acest lucru înseamnă că toate câmpurile sau valorile din tip trebuie să implementeze, de asemenea, clone pentru a deriva Clone.

Un exemplu în care este necesară Clone este atunci când apelezi metoda to_vec pe un slicing. Slicing-ul nu deține instanțele tipului pe care îl conține, dar vectorul returnat de to_vec va trebui să își dețină instanțele, așa că to_vec apelează clone la fiecare element. Astfel, tipul stocat în slicing trebuie să implementeze Clone.

Trăsătura Copy îți permite să dublezi o valoare doar prin copierea biților stocați în stivă; nu este necesar un cod arbitrar. Vezi secțiunea „Date doar pe stivă: Copy” în Capitolul 4 pentru mai multe informații despre Copy.

Trăsătura Copy nu definește nicio metodă pentru a preveni programatorii să supraîncarce acele metode și să încalce presupunerea că nu rulează niciun cod arbitrar. În acest fel, toți programatorii pot presupune că copierea unei valori va fi foarte rapidă.

Puteți deriva Copy pe orice tip ale cărui părți implementează toate Copy. Un tip care implementează Copy trebuie să implementeze, de asemenea, Clone, deoarece un tip care implementează Copy are o implementare trivială a Clone care efectuează aceeași sarcină ca Copy.

Trăsătura Copy este rareori necesară; tipurile care implementează Copy au optimizări disponibile, ceea ce înseamnă că nu trebuie să apelezi clone, făcând astfel codul mai concis.

Orice este posibil cu Copy se poate realiza și cu Clone, dar codul ar putea fi mai lent sau ar putea fi necesar să folosești clone în anumite locuri.

Hash pentru maparea unei valori către o valoare de dimensiune fixă

Trăsătura Hash îți permite să iei o instanță a unui tip de dimensiune arbitrară și să mappezi această instanță la o valoare de dimensiune fixă folosind o funcție de hash. Derivarea Hash implementează metoda hash. Implementarea derivată a metodei hash combină rezultatul apelării hash pe fiecare parte a tipului, adică toate câmpurile sau valorile trebuie să implementeze de asemenea Hash pentru a deriva Hash.

Un exemplu de când Hash este necesară este la păstrarea cheilor într-un HashMap<K, V> pentru a stoca date eficient.

Default pentru valori implicite

Trăsătura Default îți permite să creezi o valoare implicită pentru un tip. Derivarea Default implementează funcția default. Implementarea derivată a funcției default apelează funcția default pe fiecare parte a tipului, adică toate câmpurile sau valorile din tip trebuie să implementeze de asemenea Default pentru a deriva Default.

Funcția Default::default este folosită în mod obișnuit în combinație cu sintaxa de actualizare a structurilor discutată în “Crearea instanțelor din alte instanțe cu sintaxa de actualizare a structurii” section în Capitolul 5. Poți personaliza câteva câmpuri ale unei struct-uri și apoi setează și utilizează o valoare implicită pentru restul câmpurilor folosind ..Default::default().

Trăsătura Default este necesară când folosești metoda unwrap_or_default pe instanțe Option<T>, de exemplu. Dacă Option<T> este None, metoda unwrap_or_default va returna rezultatul Default::default pentru tipul T stocat în Option<T>.