通用 HashMap 不实现方法 get

Generic HashMap does not implement method get

我正在尝试创建将包含大量通用类型字段的通用结构。此结构的 AST 将由编译器插件生成,并将用于从模板渲染文本。

use std::collections::HashMap;
use std::string::ToString;


pub struct Context<Key, Value>
    where Value: ToString
{
    value: HashMap<Key, Value>,
    // here will be other fields with different generic types
}


impl <Key, Value> Context <Key, Value>
    where Value: ToString
{
    // In Jinja2 this can be written like
    // `some text before ... {{ value["foobar"] }} ... text after`
    pub fn render_to_string(&self) -> String {
        let mut r = String::new();
        r.push_str("text before ... ");

        // We see that the type of key is &'static str.
        // It is easy to determine when code written manually by human.
        // But such code will be generated by compiler plugin.
        // Here can be integer or another field from &self
        // instead of static string.
        self.value.get(&("foobar")).map(|v: &Value| r.push_str(&v.to_string()));

        r.push_str(" ... text after");
        r
    }
}


fn main() {
    let ctx1 = Context {
        value: {
            let mut v = HashMap::new();
            v.insert("foobar", 123u64);
            v
        },
    };
    println!("{:?}", ctx1.render_to_string());
}

不幸的是,Rust 拒绝编译具有此类模糊类型的代码,但它没有告诉应该为泛型定义哪些特征,而是输出:

<anon>:25:20: 25:36 error: type `std::collections::hash::map::HashMap<Key, Value>` does not implement any method in scope named `get`
<anon>:25         self.value.get(&("foobar")).map(|v: &Value| r.push_str(&v.to_string()));
                             ^~~~~~~~~~~~~~~~

是否可以在不指定 KeyValue 的确切类型的情况下修复此代码?

我们来看看 type signature of get:

impl<K, V, S> HashMap<K, V, S>
    where K: Eq + Hash,
          S: HashState
{
    fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V> 
        where K: Borrow<Q>,
              Q: Hash + Eq
    {}
}

所有这些条件都必须满足才能使用 get 方法。具体来说,在您的情况下,您需要 KeyEq + Hash:

impl <Key, Value> Context <Key, Value>
    where Key: Eq + std::hash::Hash,
          Value: ToString

这会导致一个新的错误,因为你使用了一个&str作为获取的key,所以你真的必须指定一个&str 可以 用作键:

impl<Key, Value> Context <Key, Value>
    where Key: Eq + std::hash::Hash + std::borrow::Borrow<str>,
          Value: ToString

然后,从您的 get 行中删除引用:

self.value.get("foobar")

So, path std::borrow::Borrow<str> strictly specifies that we know the real type of key at code generation stage.

指定您知道密钥的真实类型。只需要无论你的密钥是什么,都可以从&Key中借到一个&str。例如,您的 Key 可能是 StringCowString。这是必需的,因为您使用 "foobar" 作为密钥。

[The key] can be integer or another field from &self instead of static string.

如果它是 self 的另一个成员,那么您可以适当地参数化该结构:

use std::collections::HashMap;
use std::string::ToString;
use std::hash::Hash;

pub struct Context<Key, Value> {
    value: HashMap<Key, Value>,
    key: Key,
}

impl <Key, Value> Context <Key, Value>
    where Key: Eq + Hash,
          Value: ToString,
{
    pub fn render_to_string(&self) -> String {
        let mut r = "text before ... ".to_string();

        self.value.get(&self.key).map(|v| r.push_str(&v.to_string()));

        r.push_str(" ... text after");
        r
    }
}

fn main() {
    let ctx1 = Context {
        key: 42,
        value: {
            let mut v = HashMap::new();
            v.insert(42, 123u64);
            v
        },
    };
    println!("{:?}", ctx1.render_to_string());
}

to force Rust to resolve key type at first self.value.get call

这听起来像是您想要动态(运行时)键入 - 这不存在。 Rust 是一种静态类型语言,因此必须在编译时确定类型。

如果您有一组固定的键类型,您可以简单地创建一个枚举:

enum Key {
    String(String),
    Int(i32),
}

然后使用它来代替泛型。