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

Posted

技术标签:

【中文标题】在 Rust 中取消引用字符串和 HashMap【英文标题】:Dereferencing strings and HashMaps in Rust 【发布时间】:2016-09-28 00:15:27 【问题描述】:

我试图了解 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(&amp;self) -&gt; &amp;str

那么这里发生了什么?

【问题讨论】:

我认为在这里使用 Borrow 特征是错误的(无论是谁编写了HashMap::get)。基本上,通用绑定说:您可以将对任何类型的引用传递给get,如果键类型可作为该类型借用。实际上应该是:您可以将任何类型传递给get,只要该类型可强制转换为键类型即可。但是我们不能向后兼容地解决这个问题:( 【参考方案1】:

该错误是由于编译器在类型推断期间选择了泛型函数HashMap::get 而不是String。但是你想要HashMap::get 而不是str

所以改变

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

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

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

查看Playground here。

在我看来,强制 Deref&lt;Target&gt; 只能在我们知道目标类型时发生,所以当编译器试图推断要使用哪个 HashMap::get 时,它会将 &amp;r0.to_string() 视为类型 &amp;String 但从不会 @987654337 @。而&amp;'static str 没有实现Borrow&lt;String&gt;。这会导致类型错误。当我们指定HashMap::get::&lt;str&gt; 时,该函数需要&amp;str,当强制可以应用于&amp;String 以获得匹配的&amp;str

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

【讨论】:

谢谢。这就说得通了。但是,这不是意味着 Dref 有两个实现吗?一个返回 &str 和另一个 &String?该文档没有提及&String。我在这里错过了什么吗? String only implements Deref&lt;Target=str&gt;。此实现可以将&amp;String 强制转换为&amp;str,其中预计$str。见Deref coercion。【参考方案2】:

其他答案是正确的,但我想指出您有一个不需要的to_string(您已经将collect 转换为String)和另一种强制转换为&amp;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));
    

注意,地图不需要明确的类型,它会被推断出来。

【讨论】:

只是添加另一个等效的替代方法:println!(":?", roman2number.get(r0.as_str()));,新的 ::as_str 方法。【参考方案3】:

get 方法的定义如下所示

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

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

    键类型K 需要在Q 上实现Borrow trait Q 需要实现 HashEq 特征。

用您的实际类型替换它意味着键类型&amp;'static str 需要实现Borrow&lt;String&gt;。根据Borrow 的定义,这意味着&amp;'static str 需要可转换为&amp;String。但是我读过的所有文档/文本都表明,无论您在哪里使用&amp;String,您都应该使用&amp;str。因此,提供&amp;str -> &amp;String 转换毫无意义,即使有时它会让生活变得更轻松。

由于每个reference type is borrowable as a shorter lived reference type.),当&amp;'static str 是键类型时,您可以传递&amp;str,因为&amp;'static str 实现Borrow&lt;str&gt;

【讨论】:

谢谢。 Borrow 的解释很有道理!

以上是关于在 Rust 中取消引用字符串和 HashMap的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Rust 不在匹配模式中执行隐式取消引用强制?

Rust 知识积累

Rust 内存管理之声明延迟计算的常量 - Rust 实践指南

Rust Deref与自动解引用

Rust学习笔记1.基础语法

Rust学习笔记1.基础语法