我正在尝试创建包含许多通用类型字段的通用结构。此结构的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()));
^~~~~~~~~~~~~~~~
是否可以修复此代码,而无需指定Key
和Value
的确切类型?
答案 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
方法可用,所有这些条件都必须为真。具体而言,在您的情况下,您需要Key
为Eq + 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
可以是String
或CowString
。这是必需的,因为您使用"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),
}
然后使用它代替通用。