2015-11-16 18 views
15

Ho questo tratto e struttura semplice:Come specificare i parametri di durata in un tipo associato?

use std::path::{Path, PathBuf}; 

trait Foo { 
    type Item: AsRef<Path>; 
    type Iter: Iterator<Item = Self::Item>; 

    fn get(&self) -> Self::Iter; 
} 

struct Bar { 
    v: Vec<PathBuf>, 
} 

Vorrei implementare il Foo tratto per Bar:

impl Foo for Bar { 
    type Item = PathBuf; 
    type Iter = std::slice::Iter<PathBuf>; 

    fn get(&self) -> Self::Iter { 
     self.v.iter() 
    } 
} 

Comunque sto ottenendo questo errore:

error[E0106]: missing lifetime specifier 
    --> src/main.rs:16:17 
    | 
16 |  type Iter = std::slice::Iter<PathBuf>; 
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^ expected lifetime parameter 

Non ho trovato modo di specificare le durate all'interno di quel tipo associato. In particolare, voglio esprimere che l'iteratore non può sopravvivere alla durata di self.

Come è necessario modificare il tratto Foo o l'implementazione del tratto Bar per fare in modo che funzioni?

Rust playground

risposta

13

Ci sono due soluzioni al problema. Cominciamo con la più semplice:

Aggiungi una vita per il vostro tratto

trait Foo<'a> { 
    type Item: AsRef<Path>; 
    type Iter: Iterator<Item = Self::Item>; 

    fn get(&'a self) -> Self::Iter; 
} 

Ciò richiede di annotare il corso della vita in tutto il mondo si utilizza il tratto. Quando si implementa il tratto, è necessario fare un'implementazione generica:

impl<'a> Foo<'a> for Bar { 
    type Item = &'a PathBuf; 
    type Iter = std::slice::Iter<'a, PathBuf>; 

    fn get(&'a self) -> Self::Iter { 
     self.v.iter() 
    } 
} 

quando si richiede il tratto per un argomento generico, è anche necessario fare in modo che tutti i riferimenti al vostro oggetto trait hanno la stessa durata:

fn fooget<'a, T: Foo<'a>>(foo: &'a T) {} 

implementare il tratto di riferimento per il tipo

Invece di implementare il tratto per il tipo, implementare per un riferimento al tipo. Il tratto non ha mai bisogno di sapere nulla sulla vita in questo modo.

La funzione di tratto deve quindi assumere il suo argomento in base al valore. Nel tuo caso si implementare il tratto per un riferimento:

trait Foo { 
    type Item: AsRef<Path>; 
    type Iter: Iterator<Item = Self::Item>; 

    fn get(self) -> Self::Iter; 
} 

impl<'a> Foo for &'a Bar { 
    type Item = &'a PathBuf; 
    type Iter = std::slice::Iter<'a, PathBuf>; 

    fn get(self) -> Self::Iter { 
     self.v.iter() 
    } 
} 

La funzione fooget ora diventa semplicemente

fn fooget<T: Foo>(foo: T) {} 

Il problema di questo è che la funzione fooget non sa T è in realtà un &Bar. Quando chiami la funzione get, stai effettivamente uscendo dalla variabile foo. Non ti muovi fuori dall'oggetto, semplicemente muovi il riferimento. Se la tua funzione fooget prova a chiamare due volte lo get, la funzione non verrà compilata.

Se si desidera la vostra funzione fooget per accettare solo gli argomenti in cui il tratto Foo è implementata per i riferimenti, è necessario dichiarare esplicitamente questo limite:

fn fooget_twice<'a, T>(foo: &'a T) 
where 
    &'a T: Foo, 
{} 

La clausola where fa in modo che si chiama questa funzione solo per riferimenti in cui è stato implementato Foo per il riferimento anziché il tipo. Potrebbe anche essere implementato per entrambi.

Tecnicamente, il compilatore potrebbe inferire automaticamente la durata in fooget_twice così si potrebbe scrivere come

n fooget_twice<T>(foo: &T) 
where 
    &T: Foo, 
{} 

ma non è abbastanza yet intelligente.


Per i casi più complessi, è possibile utilizzare una funzione della ruggine che non è ancora implementata: Generic Associated Types (GATS). Il lavoro per questo viene monitorato in issue 44265.

+0

Ho scelto di utilizzare la prima soluzione, poiché sembra imporre meno oneri dal punto di vista delle funzioni simili a "fooget". Il tratto è anche più esplicito con la seconda soluzione. – mbrt

Problemi correlati