通用HashMap没有实现方法get

时间:2015-03-07 17:12:24

标签: rust

我正在尝试创建包含许多通用类型字段的通用结构。此结构的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的确切类型?

1 个答案:

答案 0 :(得分:3)

让我们查看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作为获取的密钥,因此您必须指定&str 可以用作一把钥匙:

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

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

self.value.get("foobar")
  

因此,路径std::borrow::Borrow<str>严格指定我们知道代码生成阶段的真实密钥类型。

指定您知道密钥的真实类型。它只需要您的密钥,&str可以从&Key借用。例如,您的Key可以是StringCowString。这是必需的,因为您使用"foobar"作为密钥。

  

[key]可以是整数或来自&amp; self的其他字段而不是静态字符串。

如果它是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());
}
  

在第一次self.value.get调用

时强制Rust解析键类型

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

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

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

然后使用它代替通用。