2016-04-07 10 views
6

Diciamo che ho qualche tratto:Implementazione Prendere in prestito <Trait> per un tipo che implementa Trait

trait MyTrait { 
    fn function1(&self); 
} 

e un certo tipo che lo implementa:

struct MyStruct { 
    number: i32, 
} 
impl MyTrait for MyStruct { 
    fn function1(&self) { 
     printn!("{}", self.number); 
    } 
} 

Ora ho un altro tipo, che vuole prendere cose che implementano MyTrait. Non gli importa se sono di proprietà o meno. Dalla lettura in giro, sembra che il modo giusto per farlo è quello di prendere lo Borrow<X> invece di X o &X o qualsiasi altra cosa. Questo gli permette di prendere le cose di tipo X, o Rc<X> o Box<X>, ecc ...

Ho questo lavoro quando X è un tipo concreto, ma come faccio a farlo funzionare quando X è un tratto?

Ecco quello che ho provato prima:

struct Consumer<T> { 
    inner: T 
} 

impl<T: Borrow<MyTrait>> Consumer<T> { 
    pub fn new(inner: T) -> Consumer<T> { 
     Consumer { 
      inner: inner 
     } 
    } 
    pub fn do_stuff(&self) { 
     self.inner.borrow().function1(); 
    } 
} 

fn main() { 
    // I want to eventually be able to swap this out for x = Rc::new(MyStruct ... 
    // but I'll keep it simple for now. 
    let x = MyStruct { number: 42 }; 
    let c = Consumer::new(x); 
    c.do_stuff(); 
} 

Questo non funziona ancora, perché MyStruct implementa Borrow<MyStruct>, ma non Borrow<MyTrait>. Va bene, quindi cerchiamo di attuare tale:

impl Borrow<MyTrait> for MyStruct { 
    fn borrow(&self) -> &MyTrait { 
     self 
    } 
} 

Ciò mi dà il seguente errore, che non capisco:

<anon>:33:5: 35:6 error: method `borrow` has an incompatible type for trait: 
expected bound lifetime parameter , 
    found concrete lifetime [E0053] 
<anon>:33  fn borrow(&self) -> &MyTrait { 
<anon>:34   self 
<anon>:35  } 
<anon>:33:5: 35:6 help: see the detailed explanation for E0053 
error: aborting due to previous error 
playpen: application terminated with error code 101 

Che cosa? Non vi è alcuna esistenza concreta menzionata in quel luogo e lo Borrow viene definito senza alcuna durata menzionata. Sono perplesso.

In primo luogo, le mie ipotesi sono corrette sul fatto che l'utilizzo di Borrow è la giusta soluzione? E se sì, come posso implementare lo Borrow di un tratto?

risposta

6

Il modo corretto di scrivere l'applicazione è questo:

impl<'a> Borrow<MyTrait + 'a> for MyStruct { 
    fn borrow(&self) -> &(MyTrait + 'a) { 
     self 
    } 
} 

oggetti Trait può essere limitato con una durata legata. Questo perché un tipo che implementa un tratto può contenere riferimenti e, in alcune situazioni, dobbiamo essere in grado di differenziare un oggetto che dipende da oggetti presi in prestito da un oggetto che non lo fa. Se il limite di durata non è specificato, penso che il valore predefinito sia 'static; tuttavia, specificando &(MyTrait + 'static) come tipo di ritorno compila (è meno generico, quindi dovresti favorire la soluzione generica sopra), quindi il problema che hai riscontrato è più sottile di quello ...

+0

Ah, grazie! Non avevo mai visto prima questa sintassi '& (MyTrait + 'a)'. L'ho visto in parametri generici ad altri tipi, o in bound type, ma mai da solo prima. –

3

Per inciso, ho trovato un modo alternativo a tale scopo, che non necessita di attuazione Borrow<MyTrait> affatto:

Invece di avere impl<T: Borrow<MyTrait> Consumer<T>, possiamo fare Consumer prendere un parametro aggiuntivo che indica quale tipo prestito effettivo sarà, e quindi vincolare tale tipo di attuare il tratto . Come questo:

impl<T: Borrow<Borrowed>, Borrowed: MyTrait> Consumer<T, Borrowed> { 

Ciò richiede Consumer di avere un membro PhantomData che fa riferimento al parametro di tipo Borrowed altrimenti inutilizzato.Ecco una piena attuazione:

struct Consumer<T, Borrowed> { 
    inner: T, 
    phantom: PhantomData<Borrowed> 
} 

impl<T: Borrow<Borrowed>, Borrowed: MyTrait> Consumer<T, Borrowed> { 
    fn new(inner: T) -> Consumer<T, Borrowed> { 
     Consumer { 
      inner: inner, 
      phantom: PhantomData 
     } 
    } 
    pub fn do_stuff(&self) { // this function is the same as before. 
     self.inner.borrow().function1(); 
    } 
} 

Questa alternativa ha la bella proprietà che permette di utilizzare i tratti con metodi generici al loro interno, in quanto non richiede la creazione di oggetti qualsiasi tratto (oggetti tratto non possono essere fatte per i tratti che hanno funzioni generiche: vedi https://doc.rust-lang.org/error-index.html#method-has-generic-type-parameters).

L'unico lato negativo è che a Consumer devono ora essere forniti suggerimenti sui parametri generici. Devi dirgli il tipo concreto coinvolto:

fn main() { 
    let x = MyStruct { number: 42 }; 
    let c = Consumer::<_, MyStruct>::new(x); 
    c.do_stuff(); 
} 
+2

Il parametro di tipo 'Borrowed' è necessario perché un tipo può implementare più istanze di' Borrow '. Se 'Borrow' aveva un tipo associato, piuttosto che un parametro type, il parametro extra type sul tuo' impl' non sarebbe necessario; potresti usare una clausola 'where' per aggiungere un limite come' T :: Target: MyTrait'. 'Deref' ha un tipo associato, ma non è implementato per' T' con 'Target = T'. Probabilmente potresti definire il tuo tratto, ma non potresti sfruttare un tratto esistente per fornire implementazioni fino a quando non verranno implementati i limiti di specializzazione o di tratto negativo. –