2014-09-06 9 views
9

mi sono imbattuto in un problema che semplifica nella seguente:Posso scrivere un Iterator che si muta e quindi produce un riferimento in se stesso?

struct MyIter { 
    vec: Vec<i8>, 
} 

fn fill_with_useful_data(v: &mut Vec<i8>) { 
    /* ... */ 
} 

impl<'a> Iterator for MyIter { 
    type Item = &'a [i8]; 

    fn next(&mut self) -> Option<&'a [i8]> { 
     fill_with_useful_data(&mut self.vec); 

     Some(&self.vec) 
    } 
} 

fn main() { 
    for slice in (MyIter { vec: Vec::new() }) { 
     println!("{}", slice); 
    } 
} 

Questo genera l'errore:

error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates 
--> src/main.rs:9:6 
    | 
9 | impl<'a> Iterator for MyIter { 
    |  ^^ unconstrained lifetime parameter 

L'idea è che l'iteratore fa un gruppo di lavoro che riflette nei suoi campi e al ogni passo, produce un riferimento in sé al codice chiamante. In questo caso, potrei modellarlo come se producessi una copia dello stato anziché il riferimento, ma facciamo finta che ciò non sia possibile o solo fastidioso.

Intuitivamente questo non dovrebbe essere un problema perché il correttore prestito può garantire che .next() non è chiamato nuovamente mentre il riferimento dato può ancora essere utilizzato per controllare lo stato del iteratore, ma il tratto Iterator non sembra prevedere quel genere di cose direttamente. Anche con alcune permutazioni come il solo aggrapparsi a un riferimento al vettore nell'iteratore stesso o rendere l'iteratore un riferimento o qualcosa per ottenere le vite nel tipo precedente, non riesco a ottenere nulla oltre il controllore del prestito.

Ho letto il blogpost "Iterators yielding mutable references" ma non sono sicuro se/come si applica al mio problema che non coinvolge riferimenti mutabili.

risposta

10

Questo non è possibile. Se fosse consentito, è possibile chiamare di nuovo next e quindi modificare i dati che sono visibili anche tramite & o addirittura invalidare completamente il riferimento. Questo perché non esiste alcuna connessione tra l'oggetto self e il riferimento restituito: non esiste una durata esplicita che li colleghi.

Per il compilatore di ragionare su questi e consentire che restituisce un riferimento in self Occorre poi una firma come

fn next(&'a mut self) -> Option<&'a [i8]> 

Tuttavia, sia diversa dalla firma del tratto che non è consentita come codice generico che richiede solo un T: Iterator<...> non può dire che ci sono diversi requisiti sull'uso del valore restituito per alcuni T; tutti devono essere gestiti in modo identico.

Il tratto Iterator è progettato per valori di ritorno indipendenti dall'oggetto iteratore, che è necessario per gli adattatori iteratore come .collect per essere corretti e sicuri. Questo è più restrittivo del necessario per molti usi (ad esempio un transitorio all'interno di un ciclo for) ma è proprio come è al momento. Non penso che abbiamo gli strumenti per generalizzare questo tratto/il ciclo for correttamente ora (in particolare, penso che abbiamo bisogno di tipi associati con vite di rango superiore), ma forse in futuro.

+0

Va bene, ho appena smesso di implementare 'Iterator' e ho creato il mio ciclo for con una macro. Non pensavo a come '.collect()' non avrebbe funzionato. – ben

+0

Questo è anche utile per gli attraversamenti in cui non è possibile utilizzare il ciclo 'for': 'while lasciare Some (elemento) = iter.next() {...' – bluss

+1

Per riferimento futuro, questo tipo di iteratore ('fn successivo (& 'un sé) ') viene spesso definito come * uno streaming iteratore *. Alcuni esempi di discussioni [1] (https://users.rust-lang.org/t/returning-borrowed-values-from-an-iterator/1096), [2] (https://www.reddit.com/r/ruggine/commenti/2t1rxx/more_general_iterator_trait /) e una [cassa che ne implementa una forma] (https://github.com/sfackler/streaming-iterator). – Shepmaster

Problemi correlati