2015-11-16 2 views
1

Я пытаюсь использовать функцию Iterator filter_map с HashMap в Rust, но я не могу ее скомпилировать. Предположим, у меня есть HashMap и список ключей. Для каждого ключа, если карта содержит ключ, я мутирую соответствующее значение на карте. Например, предположим, что значения были типа i32, и я хотел увеличить соответствующие значения.Пожизненные ошибки с использованием filter_map

use std::collections::HashMap; 

fn increment(map: &mut HashMap<i32, i32>, keys: &[i32]) { 
    for value in keys.iter().filter_map(|index| map.get_mut(index)) { 
     *value += 1; 
    } 
} 

fn main() { 
    let mut map = HashMap::new(); 
    map.insert(1,2); 
    map.insert(4,5); 
    increment(&mut map, &[0, 1, 2]); 
    assert!(*map.get(&1).unwrap() == 3); 
    assert!(*map.get(&4).unwrap() == 5); 
} 

Этот код дает мне ошибку, связанную с временем жизни:

<anon>:4:57: 4:71 error: cannot infer an appropriate lifetime for autoref due to conflicting requirements 
<anon>:4   for value in keys.iter().filter_map(|index| map.get_mut(index)) { 
                   ^~~~~~~~~~~~~~ 
<anon>:4:9: 6:10 note: in this expansion of for loop expansion 
<anon>:4:9: 6:10 note: first, the lifetime cannot outlive the call at 4:8... 
<anon>:4   for value in keys.iter().filter_map(|index| map.get_mut(index)) { 
<anon>:5    *value += 1; 
<anon>:6   } 
<anon>:4:9: 6:10 note: in this expansion of for loop expansion 
<anon>:4:9: 6:10 note: ...so that argument is valid for the call 
<anon>:4   for value in keys.iter().filter_map(|index| map.get_mut(index)) { 
<anon>:5    *value += 1; 
<anon>:6   } 
<anon>:4:9: 6:10 note: in this expansion of for loop expansion 
<anon>:4:53: 4:71 note: but, the lifetime must be valid for the method call at 4:52... 
<anon>:4   for value in keys.iter().filter_map(|index| map.get_mut(index)) { 
                  ^~~~~~~~~~~~~~~~~~ 
<anon>:4:9: 6:10 note: in this expansion of for loop expansion 
<anon>:4:53: 4:56 note: ...so that method receiver is valid for the method call 
<anon>:4   for value in keys.iter().filter_map(|index| map.get_mut(index)) { 
                  ^~~ 
<anon>:4:9: 6:10 note: in this expansion of for loop expansion 
error: aborting due to previous error 

Почему я получаю эту ошибку, и что было бы лучшим способом справиться с этой ситуацией, используя идиоматическое Rust?

ответ

1

I've desugared your original code для получения более точной информации об ошибке.Я закончил с ошибкой по следующей методике:

impl<'a, 'b> FnMut<(&'b i32,)> for MyClosure<'a> { 
    extern "rust-call" 
    fn call_mut(&mut self, (index,): (&'b i32,)) -> Option<&'a mut i32> { 
     self.map.get_mut(index) 
    } 
} 

Ошибка:

<anon>:21:18: 21:32 error: cannot infer an appropriate lifetime for autoref due to conflicting requirements [E0495] 
<anon>:21   self.map.get_mut(index) 
          ^~~~~~~~~~~~~~ 

Обычно, когда вы возвращаетесь изменяемую ссылку из функции, это связано с временем жизни параметра. Возвращенная ссылка приводит к тому, что значение, переданное как параметр функции, все еще считается заимствованным, а по правилам Руста, вы не можете брать другой займ на это значение, пока первый заимствование не выходит за рамки. Следовательно, эта программа не компилируется:

struct Foo { 
    x: i32 
} 

impl Foo { 
    fn x_mut(&mut self) -> &mut i32 { &mut self.x } 
} 

fn main() { 
    let mut foo = Foo { x: 0 }; 
    let a = foo.x_mut(); 
    foo.x_mut(); // error: cannot borrow `foo` as mutable more than once at a time 
} 

Проблема заключается в том, что вы пытаетесь вернуть изменяемую ссылку с 'a жизни, но срок службы не должным образом выразить тот факт, что вы на самом деле заимствования из MyClosure , Поэтому компилятор не учитывал бы MyClosure, заимствованный после вызова, и позволил бы снова вызвать закрытие, которое могло бы потенциально вернуть измененную ссылку, идентичную возвращаемой ранее, что приведет к сглаживанию изменчивых ссылок, что запрещено в безопасном Rust.

Для этого на работу, FnMut реализации должны быть написаны так:

impl<'a, 'b> FnMut<(&'b i32,)> for MyClosure<'a> { 
    extern "rust-call" 
    fn call_mut<'c>(&'c mut self, (index,): (&'b i32,)) -> Option<&'c mut i32> { 
     self.map.get_mut(index) 
    } 
} 

Но это не действует:

<anon>:19:5: 22:6 error: method `call_mut` has an incompatible type for trait: 
expected bound lifetime parameter , 
    found concrete lifetime [E0053] 
<anon>:19  extern "rust-call" 
<anon>:20  fn call_mut<'c>(&'c mut self, (index,): (&'b i32,)) -> Option<&'c mut i32> { 
<anon>:21   self.map.get_mut(index) 
<anon>:22  } 

Это та же ошибка, которая возникает, когда один пытается написать потоковый итератор.


† На самом деле, этот Обессахаренный код соответствует закрытию move |index| map.get_mut(index). Ваше первоначальное закрытие будет содержать поле &mut &mut HashMap<i32, i32>, а не поле &mut HashMap<i32, i32>.

1

Я не знаю, почему вы привезли filter_map на картинку здесь. Просто итерацию по клавишам и установив значение гораздо более очевидным для меня:

fn increment(map: &mut HashMap<i32, i32>, keys: &[i32]) { 
    for index in keys { 
     if let Some(value) = map.get_mut(index) { 
      *value += 1; 
     } 
    } 
} 

Ваше первое решение имеет массивное проблема - то, что произойдет, если у вас есть тот же ключ дважды? Поскольку вы создаете изменчивые ссылки, у вас будет итератор, который дважды выдал бы одну и ту же переменную ссылку, вводя aliasing. Это запрещено в безопасном ржавчине.

Я считаю, что это послужило причиной вашего сообщения об ошибке, но я признаю, что не полностью привязал его к этой проблеме. Я знаю, что в конечном итоге это вызовет проблемы.


Я не вижу эффекта наложения спектров. Мы производим изменчивые ссылки с get_mut, но сроки жизни этих ссылок не должны перекрываться, насколько я могу судить. Если я чего-то не упускаю, он следует той же логике, что и ваш код.

Чтобы сделать это более очевидным, вот ваш же код, с collect прикрепили на:

let xs: Vec<_> = keys.iter().filter_map(|index| map.get_mut(index)).collect(); 

Важной внутренней части то же самое - вы производите итератор, мощи содержать несколько изменяемых ссылки на тот же предмет.

Обратите внимание, что компилятор Rust не может (или, по крайней мере, нет) полностью проанализировать вашу программу, чтобы увидеть, что в этом случае вы потребляете одно значение и выбрасываете его, не удерживая его на несколько. Все, что он может сделать, это увидеть, что часть того, что вы сделали может привести к этому случаю и не дать вам этого сделать.

И наоборот, версия выше может никогда не привести к сглаживанию, потому что изменчивое задание не задерживается в for блоке.

+0

Я не вижу эффекта сглаживания. Мы производим mutable ссылки с get_mut, но сроки жизни этих ссылок не должны перекрываться, насколько я могу судить. Если я чего-то не упускаю, он следует той же логике, что и ваш код. – Iceberg

Смежные вопросы