2016-06-25 17 views
9

Ich habe einen hartnäckigen Kompilierungsfehler, wo Rust klagt, dass ich einen unveränderlichen Kredit habe, während ich versuche, variabel zu borgen, aber der unveränderliche Kredit ist von einem anderen Bereich, und ich bringe nichts davon herüber.Rust Borgen von einer HashMap dauert über den Rahmen hinaus, in dem es ist?

Ich habe Code, der nach einem Wert in einer Karte sucht, und wenn es vorhanden ist, gibt es zurück, andernfalls muss die Karte auf verschiedene Arten mutiert werden. Das Problem ist, dass ich keinen Weg finden kann, Rust zu bekommen, lass mich beides tun, auch wenn die beiden Operationen völlig getrennt sind.

Hier einige unsinnige Code, der die gleiche Struktur wie mein Code folgt und zeigt das Problem:

fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { 
    // extra scope in vain attempt to contain the borrow 
    { 
     match map.get(&key) { // borrow immutably 
      Some(key) => { return Some(key); }, 
      None =>(), 
     } 
    } 

    // now I'm DONE with the immutable borrow, but rustc still thinks it's borrowed 

    map.insert(0, 0); // borrow mutably, which errors 
    None 
} 

Diese Fehler heraus mit:

error[E0502]: cannot borrow `*map` as mutable because it is also borrowed as immutable 
    --> src/main.rs:17:5 
    | 
7 |   match map.get(&key) { // borrow immutably 
    |    --- immutable borrow occurs here 
... 
17 |  map.insert(0, 0); // borrow mutably, which errors 
    |  ^^^ mutable borrow occurs here 
18 |  None 
19 | } 
    | - immutable borrow ends here 

Das macht keinen Sinn für mich. Wie überlebt der unveränderliche Kredit diesen Umfang ?! Ein Zweig dieser match verlässt die Funktion durch Zurückgeben, und der andere tut nichts und verlässt den Bereich.

Ich habe gesehen, das passiert vor, wo ich fälschlicherweise das Ausleihen aus dem Bereich in einer anderen Variable schmuggelte, aber das ist hier nicht der Fall!

Wahr, der Kredit entzieht sich dem Geltungsbereich über die Return-Anweisung, aber es ist lächerlich, dass das Blocken weiter unten in der Funktion blockiert - das Programm kann möglicherweise nicht zurückkehren UND weitermachen! Wenn ich dort etwas anderes zurückgebe, verschwindet der Fehler, also denke ich, dass der Border-Checker damit aufgelegt wird. Das fühlt sich für mich wie ein Käfer an.

Leider konnte ich keine Möglichkeit finden, dies neu zu schreiben, ohne den gleichen Fehler zu bekommen, so dass es ein besonders böser Bug ist, wenn das der Fall ist.

+0

Leider .entry() für nicht richtig ist, was diese Funktion tun muss. Ich kenne das nicht-lexikalische Bereichsproblem, und normalerweise kann ich es umgehen, aber in diesem Fall war ich nicht in der Lage, etwas zu finden, das nicht viel Arbeit vervielfältigt, egal wie hässlich der Workaround ist ... Auch das Hinzufügen eines Scopes funktioniert normalerweise um das Problem, aber hier ist es nicht; sogar das Verschieben eines Borgs zu einer anderen Funktion hilft nicht. –

Antwort

6

Dies ist eine known issue, die sehr wahrscheinlich von non-lexical scopes gelöst wird, die wiederum auf MIR basiert. Wenn es so ist, dass Sie in denselben Schlüssel einfügen, den Sie suchen, würde ich Sie ermutigen, use the entry API.

Sie können eine kleine Ineffizienz hinzufügen, um dies zu umgehen. Die allgemeine Idee besteht darin, einen booleschen Wert hinzuzufügen, der angibt, ob ein Wert vorhanden war oder nicht. Dieser boolean nicht hängt nicht mit einer Referenz auf, so dass es keine borrow:

use std::collections::BTreeMap; 

fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { 
    if map.contains_key(&key) { 
     return map.get(&key); 
    } 

    map.insert(0, 0); 
    None 
} 

fn main() { 
    let mut map = BTreeMap::new(); 
    do_stuff(&mut map, 42); 
    println!("{:?}", map) 
} 

Ähnliche Fälle mit einem Vektor anstelle ein HashMap kann unter Verwendung des Index des Elements anstelle der Referenz gelöst werden. Wie im obigen Fall kann dies ein wenig Ineffizienz bewirken, da die Schichtgrenzen erneut überprüft werden müssen.

Statt

fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 { 
    match container.iter_mut().find(|e| **e == 5) { 
     Some(element) => element, 
     None => { 
      container.push(5); 
      container.last_mut().unwrap() 
     } 
    } 
} 

Sie schreiben:

fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 { 
    let idx = container.iter().position(|&e| e == 5).unwrap_or_else(|| { 
     container.push(5); 
     container.len() - 1  
    }); 
    &mut container[idx] 
} 

Beachten Sie, dass nicht-lexikalische Leben gearbeitet werden.In nächtlichen Rust, beide Original-Code Beispiele kompilieren, wie sie ist, wenn NLL aktiviert ist:

#![feature(nll)] 

use std::collections::BTreeMap; 

fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { 
    if let Some(key) = map.get(&key) { 
     return Some(key); 
    } 

    map.insert(0, 0); 
    None 
} 
#![feature(nll)] 

fn find_or_create_five(container: &mut Vec<u8>) -> &mut u8 { 
    match container.iter_mut().find(|e| **e == 5) { 
     Some(element) => element, 
     None => { 
      container.push(5); 
      container.last_mut().unwrap() 
     } 
    } 
}