Разыменование строк и HashMaps в Rust
Я пытаюсь понять, как работают HashMaps в Rust, и я придумал этот пример.
use std::collections::HashMap;
fn main() {
let mut roman2number: HashMap<&'static str, i32> = HashMap::new();
roman2number.insert("X", 10);
roman2number.insert("I", 1);
let roman_num = "XXI".to_string();
let r0 = roman_num.chars().take(1).collect::<String>();
let r1: &str = &r0.to_string();
println!("{:?}", roman2number.get(r1)); // This works
// println!("{:?}", roman2number.get(&r0.to_string())); // This doesn't
}
Когда я пытаюсь скомпилировать код с последней строкой без комментариев, я получаю следующую ошибку
error: the trait bound `&str: std::borrow::Borrow<std::string::String>` is not satisfied [E0277]
println!("{:?}", roman2number.get(&r0.to_string()));
^~~
note: in this expansion of format_args!
note: in this expansion of print! (defined in <std macros>)
note: in this expansion of println! (defined in <std macros>)
help: run `rustc --explain E0277` to see a detailed explanation
Раздел реализации черты документов дает разыменование как fn deref(&self) -> &str
Так что здесь происходит?
3 ответа
Ошибка вызвана этой универсальной функцией HashMap::get
над String
выбирается компилятором при выводе типа. Но ты хочешь HashMap::get
над str
,
Так что просто поменяй
println!("{:?}", roman2number.get(&r0.to_string()));
в
println!("{:?}", roman2number.get::<str>(&r0.to_string()));
чтобы сделать это явным. Это помогает компилятору выбрать правильную функцию.
Проверьте игровую площадку здесь.
Мне кажется, что принуждение Deref<Target>
может произойти только тогда, когда мы знаем целевой тип, поэтому, когда компилятор пытается определить, какой HashMap::get
использовать, он видит &r0.to_string()
как тип &String
но никогда &str
, А также &'static str
не реализует Borrow<String>
, Это приводит к ошибке типа. Когда мы указываем HashMap::get::<str>
эта функция ожидает &str
когда принуждение может быть применено к &String
чтобы получить соответствие &str
,
Вы можете проверить Deref
принуждение и String
Дереф для более подробной информации.
Другие ответы верны, но я хотел бы отметить, что у вас есть ненужные to_string
(ты уже collect
в String
) и альтернативный способ принуждения к &str
, с помощью as
:
let r0: String = roman_num.chars().take(1).collect();
println!("{:?}", roman2number.get(&r0 as &str));
В этом случае, я бы просто переписал карту, чтобы она содержала char
в качестве ключа, хотя:
use std::collections::HashMap;
fn main() {
let mut roman2number = HashMap::new();
roman2number.insert('X', 10);
roman2number.insert('I', 1);
let roman_num = "XXI";
for c in roman_num.chars() {
println!("{:?}", roman2number.get(&c));
}
}
Обратите внимание, что нет необходимости иметь явный тип для карты, это будет вывод.
Определение get
Метод выглядит следующим образом
fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V> where K: Borrow<Q>, Q: Hash + Eq
Первая часть - это тип объекта, который вы передаете: Q
, Есть ограничения на Q
, Условия на Q
это что
- тип ключа
K
необходимо реализоватьBorrow
черта надQ
Q
необходимо реализоватьHash
а такжеEq
черты.
Замена этого фактическими типами означает, что тип ключа &'static str
необходимо реализовать Borrow<String>
, По определению Borrow
это означает, что &'static str
должен быть конвертируемым в &String
, Но все документы / тексты, которые я прочитал, утверждают, что везде, где вы будете использовать &String
вы должны использовать &str
вместо. Так что нет смысла предлагать &str
-> &String
обращение, даже если это иногда делает жизнь немного легче.
Поскольку каждый ссылочный тип может быть заимствован как ссылочный тип с более коротким сроком действия.), Вы можете передать &str
когда &'static str
это тип ключа, потому что &'static str
инвентарь Borrow<str>