2015-08-17 11 views
50

In this question, è sorto un problema che poteva essere risolto modificando un tentativo di utilizzare un parametro di tipo generico in un tipo associato. Questo ha spinto la domanda "Perché un tipo associato è più appropriato qui?", Il che mi ha fatto venir voglia di saperne di più.Quando è opportuno utilizzare un tipo associato rispetto a un tipo generico?

Il RFC that introduced associated types dice:

Questo RFC chiarisce corrispondente tratto da:

  • trattamento tutti i parametri di tipo specifico tratto tipi di ingresso e
  • Fornire tipi associati, che sono uscita tipi.

Il RFC utilizza una struttura grafico come un esempio di motivazione, e questo viene utilizzato anche in the documentation, ma devo ammettere di non apprezzare appieno i vantaggi della versione tipo associato rispetto alla versione tipo parametrizzato . La cosa principale è che il metodo distance non ha bisogno di preoccuparsi del tipo Edge. Questo è bello, ma sembra un po 'superficiale di un motivo per avere tipi associati.

Ho trovato che i tipi associati sono piuttosto intuitivi da utilizzare nella pratica, ma mi trovo a dover lottare per decidere dove e quando dovrei usarli nella mia API.

Durante la scrittura del codice, quando dovrei scegliere un tipo associato su un parametro di tipo generico e quando dovrei fare il contrario?

risposta

28

Questo è ora descritto in the second edition of The Rust Programming Language. Tuttavia, tuffiamoci un po 'oltre.

Iniziamo con un esempio più semplice.

Quando è opportuno utilizzare un metodo tratto?

Ci sono diversi modi per fornire tardiva:

trait MyTrait { 
    fn hello_word(&self) -> String; 
} 

Oppure:

struct MyTrait<T> { 
    t: T, 
    hello_world: fn(&T) -> String, 
} 

impl<T> MyTrait<T> { 
    fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait<T>; 

    fn hello_world(&self) -> String { 
     (self.hello_world)(self.t) 
    } 
} 

prescindere da qualunque strategia di attuazione/prestazioni, entrambi i brani sopra consentono all'utente di specificare in un modalità dinamica come dovrebbe comportarsi hello_world.

L'unica differenza (semanticamente) è che l'attuazione trait garantisce che, per un dato tipo T attuazione del trait, hello_world avrà sempre lo stesso comportamento che l'attuazione struct permette di avere un comportamento differente su una base per esempio.

Se l'utilizzo di un metodo è appropriato o meno dipende dal caso d'uso!

Quando è opportuno utilizzare un tipo associato?

Analogamente ai trait metodi di cui sopra, un tipo associato è una forma di tardiva (anche se avviene a compilazione), consentendo all'utente di trait per specificare per una determinata istanza quale tipo di sostituire. Non è l'unico modo (quindi la domanda):

trait MyTrait { 
    type Return; 
    fn hello_world(&self) -> Self::Return; 
} 

Oppure:

trait MyTrait<Return> { 
    fn hello_world(&Self) -> Return; 
} 

sono equivalenti alla tardiva dei metodi di cui sopra:

  • il primo impone che, per un dato Self c'è un singolo Return associato
  • il secondo, invece, consente di implementare MyTrait per Self per multipli Return

Quale forma è più appropriata dipende dal fatto che abbia senso applicare l'unicità o meno. Per esempio:

  • Deref utilizza un tipo associato perché senza unicità il compilatore sarebbe impazzito durante l'inferenza
  • Add utilizza un tipo associato perché il suo autore pensava che date le due argomenti ci sarebbe una logica tipo di ritorno

Come potete vedere, mentre Deref è un caso d'uso ovvio (vincolo tecnico), il caso di Add è meno chiare: forse avrebbe senso per i32 + i32 a cedere sia i32 o Complex<i32> a seconda del contesto? Ciononostante, l'autore ha esercitato il proprio giudizio e ha deciso che non era necessario sovraccaricare il tipo di ritorno per le aggiunte.

La mia posizione personale è che non c'è una risposta giusta.Tuttavia, al di là dell'argomento dell'unicità, vorrei menzionare che i tipi associati rendono più semplice l'utilizzo della caratteristica in quanto riducono il numero di parametri che devono essere specificati, quindi nel caso in cui i vantaggi della flessibilità dell'uso di un parametro di tratto regolare non siano evidenti, suggerire di iniziare con un tipo associato.

19

I tipi associati sono un meccanismo di raggruppamento , quindi devono essere utilizzati quando è opportuno raggruppare i tipi.

Il tratto Graph introdotto nella documentazione è un esempio di questo. Si desidera che un Graph sia generico, ma una volta che si dispone di un tipo specifico di Graph, non si desidera che i tipi Node o Edge siano più diversi. Un particolare Graph non vorrà variare questi tipi all'interno di una singola implementazione e, in effetti, vuole che siano sempre gli stessi. Sono raggruppati insieme, o si potrebbe anche dire associati.

+2

Mi ci è voluto un po 'di tempo per capire. Per me sembra più come definire più tipi contemporaneamente: Edge e Node non hanno senso dal grafico. – tafia

-1

vengo da un mondo Swift/iOS, ma penso che si applicano i concetti:

  • Per un singola funzione si usa farmaci generici per vincolare più parametri di una funzione.

  • Per un tipo si utilizzerà un tipo associato, in modo da poter vincolare i parametri tra più funzioni di quel tipo.

Problemi correlati