在Rust中解引用字符串和HashMap

16

我正在尝试理解Rust中HashMap的工作原理,我想出了这个例子。

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

文档 的 Trait implementation 部分中,对解引用的说明如下:fn deref(&self) -> &str

那么这里到底发生了什么呢?


我认为(无论是谁编写了HashMap :: get)在这里使用Borrow特质是错误的。基本上,泛型限制表示:您可以将对任何类型的引用传递给get,如果键类型可借用为该类型。实际上应该是:只要该类型可以强制转换为键类型,就可以将任何类型传递给get。但是我们无法以向后兼容的方式修复此问题 :( - oli_obk
3个回答

23

这个错误是由于编译器在类型推断期间选择了String的一般函数HashMap::get,而你想要使用strHashMap::get

所以只需更改

println!("{:?}", roman2number.get(&r0.to_string()));

println!("{:?}", roman2number.get::<str>(&r0.to_string()));

为了明确表达。 这有助于编译器选择正确的函数。

请查看此处的Playground

在我看来,当我们知道目标类型时才可以发生强制转换Deref<Target>,因此当编译器尝试推断要使用哪个HashMap :: get时,它将&r0.to_string()视为类型&String而不是&str。 而且,&'static str不实现Borrow<String>。 这导致类型错误。 当我们指定HashMap :: get ::<str>时,该函数期望&str,这时可以将强制转换应用于&String以获得匹配的&str

您可以查看Deref coercionString Deref了解更多详情。


谢谢,那很有道理。但是,这难道不意味着Dref有两个实现吗?一个返回&str,另一个返回&String?文档没有提到&String。我在这里漏掉了什么吗? - skanur
String 只实现了 Deref<Target=str>。这个实现使得可以将 &String 强制转换为期望 $str&str。请参见 Deref coercion - WiSaGaN

7
其他答案都是正确的,但我想指出你有一个不必要的to_string(你已经收集到了一个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));
    }
}

注意,对于Map对象来说并不需要明确指定类型,它会被自动推断。


2
还有另一种等效的选择:println!("{:?}", roman2number.get(r0.as_str()));,使用较新的 ::as_str 方法。 - c0g

4
< p > "get" 方法的定义如下所示 < /p>
fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V> where K: Borrow<Q>, Q: Hash + Eq

第一部分是您传递的对象类型:Q。对Q有限制条件。对Q的条件是:
  1. 键类型K需要在Q上实现Borrow特性
  2. Q需要实现HashEq特性。
将其替换为实际类型意味着键类型&'static str需要实现Borrow<String>。根据Borrow的定义,这意味着&'static str需要可转换为&String。但我读过的所有文档/文本都声明,无论何时使用&String,都应改用&str。因此,即使有时可能会使生活变得更轻松,也没有提供&str->&String转换的意义。
由于每个引用类型都可以作为寿命较短的引用类型进行借用,因此当&'static str是键类型时,可以传递&str,因为&'static str实现了Borrow<str>

谢谢。Borrow 的解释很合理! - skanur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接