2016-06-25 14 views
9

Ho un errore di compilazione persistente in cui Rust si lamenta del fatto che ho un prestito immutabile mentre sto cercando di mutuarlo in modo mutevole, ma il prestito immutabile proviene da un altro ambito e non sto portando nulla da esso.L'indebitamento di una HashMap dura oltre lo scopo in cui si trova?

Ho un codice che controlla un valore in una mappa e, se presente, lo restituisce, altrimenti è necessario modificare la mappa in vari modi. Il problema è che non riesco a trovare un modo per far sì che Rust mi faccia fare entrambe le cose, anche se le due operazioni sono completamente separate.

Ecco un codice privo di senso che segue la stessa struttura come il mio codice e presenta il problema:

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 
} 

Questo errori con:

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 

Questo non ha alcun senso per me. In che modo il mutuo immutabile sopravvive a tale scopo ?! Un ramo di tale match esce dalla funzione tramite return e l'altro non fa nulla e lascia l'ambito.

Ho visto questo accadere prima dove ho erroneamente contrabbandato il prestito fuori dallo scope in qualche altra variabile, ma non è questo il caso!

È vero, il prestito sta sfuggendo allo scope tramite l'istruzione return, ma è ridicolo che blocchi i prestiti più in basso nella funzione - il programma non può eventualmente tornare AND continuare ad andare avanti! Se restituisco qualcos'altro lì, l'errore scompare, quindi penso che questo sia ciò su cui il controllore del prestito viene bloccato. Mi sembra un insetto.

Sfortunatamente, non sono stato in grado di trovare alcun modo per riscrivere questo senza colpire lo stesso errore, quindi è un bug particolarmente cattivo se questo è il caso.

+0

Purtroppo, .entry() non è giusto per quello che questa funzione deve fare. Sono a conoscenza del problema dei bachi non lessicali, e di solito posso aggirarlo, ma in questo caso, non sono stato in grado di trovare qualcosa che non faccia un po 'di lavoro duplicato, non importa quanto brutta la soluzione è ... Inoltre di solito aggiungendo un ambito si aggira il problema, ma qui non lo fa; anche spostare un prestito in un'altra funzione non aiuta. –

risposta

6

Questo è uno known issue che è molto probabile che sia risolto da non-lexical scopes, che è a sua volta previsto su MIR. Se accade che tu stia inserendo la stessa chiave che stai cercando, ti incoraggio a use the entry API.

È possibile aggiungere un po 'di inefficienza per ovviare a questo problema per ora. L'idea generale è di aggiungere un valore booleano che indichi se un valore era presente o meno. Questo booleana non aggrapparsi a un riferimento, quindi non c'è prestito:

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) 
} 

casi simili con un vettore anziché un HashMap può essere risolto utilizzando l'indice dell'elemento al posto del riferimento. Come nel caso precedente, questo può introdurre un po 'di inefficienza a causa della necessità di controllare nuovamente i limiti delle sezioni.

Invece di

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() 
     } 
    } 
} 

Si può scrivere:

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] 
} 

noti che vite non lessicali si sta lavorando.In Rust notte, entrambi esempi di codice originali compilazione così com'è quando NLL è abilitata:

#![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() 
     } 
    } 
}