2016-04-09 15 views
1

Warum kann ich nicht push zu diesem Vektor während inspect und tun contains darauf während skip_while?Rust Inspect Iterator: kann `*` nicht als unveränderlich ausleihen, weil es auch als veränderbar geliehen wird

struct Chain { 
    n: u32, 
} 

impl Chain { 
    fn new(start: u32) -> Chain { 
     Chain { n: start } 
    } 
} 

impl Iterator for Chain { 
    type Item = u32; 

    fn next(&mut self) -> Option<u32> { 
     self.n = digit_factorial_sum(self.n); 
     Some(self.n) 
    } 
} 

Nun, was ich es take tun mag, während der Iterator eindeutige Werte produziert:

Ich habe meinen eigenen Iterator für meine eigene Struktur Chain wie diese umgesetzt werden. So bin ich inspect die Kette -ing und auf einen Vektor schieben und sie dann in einem take_while Umfang Überprüfung:

let mut v = Vec::with_capacity(terms); 
Chain::new(i) 
    .inspect(|&x| { 
     v.push(x) 
    }) 
    .skip_while(|&x| { 
     return v.contains(&x); 
    }) 

jedoch spuckt die Rust Kompilierung aus diesem Fehler:

error: cannot borrow `v` as immutable because it is also borrowed as mutable [E0502] 
... 
borrow occurs due to use of `v` in closure 
    return v.contains(&x); 
     ^
previous borrow of `v` occurs here due to use in closure; the mutable borrow prevents subsequent moves, borrows, or modification of `v` until the borrow ends 
    .inspect(|&x| { 
     v.push(x) 
    }) 

Offensichtlich I don verstehe das Konzept des "Ausleihens" nicht. Was mache ich falsch?

+0

Wahrscheinlich hat das Problem damit zu tun, dass Sie versuchen, eine Variable zu schließen, die sich irgendwo in einem anderen Bereich ('v') befindet. Das ist in Rust sehr wichtig. Daher müssen wir sehen, woher 'v' kommt, wie es deklariert wurde und wie Sie es im aktuellen Bereich referenzieren. –

+0

@SimonWithehead bearbeitet, um 'v' einzuschließen. Ich muss es schon früher weggelassen haben. – alt

Antwort

3

Das Problem hier ist, dass Sie versuchen, eine veränderbare und eine unveränderliche Referenz auf die gleiche Variable zu erstellen, was eine Verletzung der Rust-Borrowing-Regeln ist. Und Rustc sagt das wirklich sehr deutlich zu dir.

let mut v = Vec::with_capacity(terms); 
Chain::new(i) 
    .inspect(|&x| { 
     v.push(x) 
    }) 
    .skip_while(|&x| { 
     return v.contains(&x); 
    }) 

Hier Sie versuchen v in zwei Verschlüssen zu verwenden, zuerst in inspect() Argumente, die zweiten in skip_while() Argumente. Non-move Verschlüsse erfassen ihre Umgebung als Verweis, so dass die Umgebung des ersten Abschlusses &mut v enthält und die des zweiten Abschlusses &v enthält. Closures werden in demselben Ausdruck erstellt, also selbst wenn es garantiert war, dass inspect() lief und den Borrow vor skip_while() ablegte (was ich nicht der tatsächliche Fall bin, weil dies Iterator-Adapter sind und sie überhaupt nicht ausgeführt werden, bis der Iterator ist konsumiert), ist dies aufgrund lexikalischer Ausleihregeln verboten.

Leider ist dies eines dieser Beispiele, wenn der Border-Checker übermäßig streng ist. Was können Sie tun, ist RefCell zu verwenden, die Mutation durch eine gemeinsame Referenz erlaubt, führt aber eine gewisse Laufzeit Kosten:

use std::cell::RefCell; 

let mut v = RefCell::new(Vec::with_capacity(terms)); 
Chain::new(i) 
    .inspect(|x| v.borrow_mut().push(*x)) 
    .skip_while(|x| v.borrow().contains(x)) 

Ich denke es möglich sein kann Laufzeitstrafe von RefCell und verwenden UnsafeCell stattdessen zu vermeiden, denn wenn der Iterator verbraucht wird, werden diese Closures nur nacheinander und nicht gleichzeitig ausgeführt, daher sollten niemals gleichzeitig veränderliche und unveränderbare Referenzen ausstehen. Es könnte wie folgt aussehen:

use std::cell::UnsafeCell; 

let mut v = UnsafeCell::new(Vec::with_capacity(terms)); 
Chain::new(i) 
    .inspect(|x| unsafe { (&mut *v.get()).push(*x) }) 
    .skip_while(|x| unsafe { (&*v.get()).contains(x) }) 

Aber ich kann falsch sein, und trotzdem, der Aufwand für RefCell ist nicht so hoch, es sei denn, dieser Code in einem wirklich enge Schleife ausgeführt wird, so dass Sie nur als UnsafeCell verwenden sollten ein letzter Ausweg, nur wenn nichts anderes funktioniert, und äußerste Vorsicht bei der Arbeit mit ihm.

+0

Dies ist eine gute Erklärung, aber beide Lösungen brechen den Iterator aus irgendeinem Grund. Ich kann es nicht herausfinden. Es ist fast so, als ob 'skip_while' die Iteration nicht auf die übliche Weise stoppt und für immer sucht. Entweder das oder es ist über 100x langsamer (ich habe ~ 1min gewartet und es ist nie fertig). – alt

+0

Ich war in der Lage, eine Lösung zu implementieren, indem ich alle 'push' /' contains' Logik in die Iterator 'next()' Funktion verlagerte: https://github.com/JacksonGariety/euler.rs/blob/master/src/ solution_074.rs – alt

+0

Das ist ziemlich effizient, aber ich würde die Logik gerne in eine 'skip_while' verschieben, wenn möglich. Wenn ich beide Implementierungen verwende, überspringt das 'skip_while' entweder alle Iterationen oder der Iterator läuft für immer. – alt