在 Rust 中取消引用字符串和 HashMap

Dereferencing strings and HashMaps in 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

docs 的 Trait 实现部分给出了解除引用 fn deref(&self) -> &str

那么这里发生了什么?

错误是由于 String 上的泛型函数 HashMap::get 在类型推断期间被编译器 select 编辑。但是你想要 HashMap::get 而不是 str.

所以改变一下

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

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

使其明确。这有助于编译器 select 正确的函数。

查看 Playground here

在我看来强制转换 Deref<Target> 只有在我们知道目标类型时才会发生,因此当编译器试图推断要使用哪个 HashMap::get 时,它会将 &r0.to_string() 视为输入 &String 但永远不要输入 &str。并且 &'static str 没有实现 Borrow<String>。这会导致类型错误。当我们指定 HashMap::get::<str> 时,此函数期望 &str,此时可以对 &String 进行强制转换以获得匹配的 &str.

您可以查看 Deref coercion and String Deref 了解更多详情。

get 方法的定义如下

fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V> where K: Borrow<Q>, Q: Hash + Eq

第一部分是您传递的对象类型:QQ 有限制。 Q 的条件是

  1. 键类型 K 需要在 Q
  2. 上实现 Borrow 特性
  3. Q 需要实现 HashEq 特征。

将其替换为您的实际类型意味着键类型 &'static str 需要实现 Borrow<String>。根据 Borrow 的定义,这意味着 &'static str 需要转换为 &String。但我读过的所有 docs/texts 都指出,无论你在何处使用 &String,你都应该使用 &str。因此,提供 &str -> &String 转换毫无意义,即使有时它会让生活变得更轻松。

因为每个 reference type is borrowable as a shorter lived reference type.),当 &'static str 是键类型时,你可以传递 &str,因为 &'static str 实现了 Borrow<str>

其他答案是正确的,但我想指出您有一个不需要的 to_string(您已经 collect 编辑成 String)和替代方法使用 as:

强制转换为 &str
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));
    }
}

请注意,地图无需显式类型,系统会自动推断。