Проблема с Iterator

Я пытаюсь преобразовать вектор пар &str в HashMap со следующим фрагментом кода:

use std::collections::HashMap;

fn main() {
  let pairs = vec!(("foo", "bar"), ("toto", "tata"));
  let map: HashMap<&str, &str> = pairs.iter().collect();
  println!("{:?}", map);
}

Однако компиляция не выполняется с этой ошибкой:

<anon>:5:47: 5:56 error: the trait `core::iter::FromIterator<&(&str, &str)>` is not implemented for the type `std::collections::hash::map::HashMap<&str, &str>` [E0277]
<anon>:5   let map: HashMap<&str, &str> = pairs.iter().collect();

Однако, если я добавляю .cloned() перед вызовом collect(), все работает нормально:

...
let map: HashMap<&str, &str> = pairs.iter().cloned().collect();
...

Даже если я понимаю сообщение об ошибке (нет реализации признака FromIterator<&(&str, &str)> для типа HashMap<&str, &str>), я не понимаю, откуда приходит тип &(&str, &str) (согласно сигнатуре метода в документации Rust ) и почему вызов cloned() устраняет эту проблему.

Ответ 1

Тип &(&str, &str) происходит от того, что iter() возвращает Vec:

fn iter(&self) -> Iter<T>

где Iter<T> реализует Iterator<Item=&T>:

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T
    ...
}

Другими словами, iter() на векторе возвращает итератор, дающий ссылки в вектор.

cloned() решает проблему, потому что это адаптер итератора, который преобразует Iterator<Item=&T> в Iterator<Item=T>, если T является клонируемым. Вы можете думать об этом как сокращение для map(|v| v.clone()):

let v1: Vec<i32> = vec![1, 2, 3, 4];
let v2: Vec<_> = v1.iter().cloned().collect();
let v3: Vec<_> = v1.iter().map(|v| v.clone()).collect();
assert_eq!(v2, v3);

Бывает, что (&str, &str) является клонированным, потому что каждый компонент tuple также является клонированным (все ссылки есть), поэтому cloned() возвращает объект, который реализует Iterator<Item=(&str, &str)> - именно то, что collect() необходимо создать HashMap.

В качестве альтернативы вы можете использовать into_iter() для получения Iterator<Item=T> из Vec<T>, но тогда будет использован исходный вектор:

use std::collections::HashMap;

fn main() {
    let pairs = vec!(("foo", "bar"), ("toto", "tata"));
    let map: HashMap<&str, &str> = pairs.into_iter().collect();
    println!("{:?}", map);
}

Ответ 2

Проблема заключается в том, что, хотя ссылки могут быть скопированы, кортежи не могут.

Однако, если вам больше не нужны пары, вы можете выполнять итерацию по значениям:

use std::collections::HashMap;

fn main() {
  let pairs = vec!(("foo", "bar"), ("toto", "tata"));
  let map: HashMap<&'static str, &'static str> = pairs.into_iter().collect();
  println!("{:?}", map);
}