2016-06-25 21 views
9

Mam trwały błąd kompilacji, w którym Rust skarży się, że mam niezmienny pożyczkę, podczas gdy ja próbuję się pożyczyć, ale niezmienna zapłata pochodzi z innego zakresu i nic z tego nie wynika.Pożyczyć Rust z HashMap trwa poza zakres jest w?

Mam kod, który sprawdza wartość na mapie, a jeśli jest obecna, zwraca ją, w przeciwnym razie musi mutować mapę na różne sposoby. Problem polega na tym, że nie potrafię znaleźć sposobu, by Rust pozwolił mi zrobić jedno i drugie, mimo że te dwie operacje są całkowicie oddzielne.

Oto niektóre bezsensowne kod, który ma taką samą strukturę jak mojego kodu i wykazuje 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 
} 

błędom się z:

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 

To nie ma żadnego sensu dla mnie. W jaki sposób niezmienna pożycza przeżyć ten zakres ?! Jedna gałąź tego match wychodzi z funkcji przez return, a druga nic nie robi i opuszcza zasięg.

Widziałem to już wcześniej, gdzie błędnie przeszmuglowałem pożyczkę z zakresu w jakiejś innej zmiennej, ale tak nie jest w tym przypadku!

To prawda, pożyczka ucieka z zakresu poprzez oświadczenie zwrotne, ale to śmieszne, że blokuje pożyczanie dalej w funkcji - program nie może wrócić i kontynuować! Jeśli zwrócę tu coś innego, błąd zniknie, więc myślę, że to jest to, na czym polega tester pożyczkowy. To jest dla mnie jak błąd.

Niestety, nie udało mi się znaleźć żadnego sposobu na przepisanie tego bez trafienia tego samego błędu, więc jest to szczególnie nieprzyjemny błąd, jeśli tak jest.

+0

Niestety .entry() nie jest odpowiednia w stosunku do tego, co ta funkcja musi zrobić. Zdaję sobie sprawę z nieekslikowego problemu z zakresami i zazwyczaj potrafię go obejść, ale w tym przypadku nie byłem w stanie wymyślić czegoś, co nie powoduje całej duplikacji pracy, bez względu na to, jak brzydkie obejście jest ... Zwykle dodanie zakresu działa wokół problemu, ale tutaj tak się nie dzieje; nawet przenoszenie jednej pożyczki na inną funkcję nie pomaga. –

Odpowiedz

6

Jest to known issue, który najprawdopodobniej zostanie rozwiązany przez non-lexical scopes, który sam jest przewidziany na MIR. Jeśli tak się składa, że ​​wstawiasz do tego samego klucza, którego szukasz, zachęcam cię do: use the entry API.

Możesz dodać odrobinę nieefektywności do obejścia tego na teraz. Ogólną ideą jest dodanie wartości logicznej, która mówi, czy wartość była obecna, czy nie. Ta logiczna nie powiesić na punkt odniesienia, więc nie ma pożyczyć:

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

podobne przypadki z wektorem zamiast HashMap można rozwiązać za pomocą indeksu elementu zamiast odniesienia. Podobnie jak w przypadku powyżej, może to wprowadzić trochę nieefektywności ze względu na konieczność ponownego sprawdzenia granic plastra.

Zamiast

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

Można napisać:

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

Zauważmy, że okresy non-leksykalne są pracował.W nocnej Rust, zarówno oryginalne przykłady kodu skompilować jak jest gdy NLL jest włączona:

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