我要使用HashMap。其中key是一个struct,其中包括了一个String类型(就是非Copy的):
struct Key {
uid: u64,
name: String,
}
然后我想定义一个get函数:
fn get(dict: &HashMap, uid: u64, name: &String) -> Option<Value> {
let key = Key { uid, name }; // <-- name.clone()?
dict.get(&key)
}
这个函数的最后一个参数,name,我希望是&String
而不是String
,这样外面的调用者就不用消耗掉这个name。
然后问题就来了,在函数内部定义这个临时的key变量时,这个name怎么处理?难道要clone()吗?这个代价就太高了。
请问下,这种需求,应该怎么处理?
评论区
写评论name可否换成Cow类型
明白了!
--
👇
hangj: 也就是:如果我们实现了
Borrow<Inner> for Outer
, 那么Equivalent<Outer> for Inner
会被自动实现也就是:如果我们实现了
Borrow<Inner> for Outer
, 那么Equivalent<Outer> for Inner
会被自动实现可以编译过,因为
Equivalent
实现了这个所以它兼容了
Borrow
的情况,测试代码:--
👇
LongRiver: 那在用户自己的app里,如果也定义了类似的Inner和Outer并且也实现了Borrow,然后在代码里用Inner作为key去调用HashMap::get();那么如果HashMap::get()换了原型后,用户的代码也就编译不过了吧。
--
👇
hangj: 如果仔细看标准库里所有
Borrow
trait 的实现(Implementors),你会发现它们都是impl Borrow<Inner> for Outer
这种模式的,其中 Inner 和 Outer 的关系是这样:这种情况下实现
Borrow<Inner> for Outer
很简单Borrow<str> for String
Borrow<[T]> for Vec<T>
等等都是如此,从 Outer borrow 出它内部的数据而已但是对于没有包含关系的
TmpKey
和Key
, 要实现Borrow<TmpKey> for Key
是几乎不可能做到的--
👇
LongRiver: 虽然难,但应该也是可以做到的吧。我看Borrow的文档里,就举了String和str的例子。
我看SO上的那个回答也说了,非常难实现Borrow。
我的意思是如果这么改,至少在理论上,是不兼容了。实际上可能会影响非常小。
--
👇
hangj: 问题是
Borrow<TmpKey> for Key
如何实现?这几乎是无法做到的。如果可以做到的话,Equavlant
就没必要存在了--
👇
LongRiver: 我说的“原来的代码”,指的是使用了hashmap的app的代码,而不是 hashmap自己的代码。
比如我之前写了个app,使用hashmap,并且实现了
Borrow<TmpKey> for Key
。那么如果HashMap::get()的原型中参数key的约束改了,改成了Equavlant。那么我原来的app代码还能编译通过吗?
我是猜的,感觉就不会通过了。
那在用户自己的app里,如果也定义了类似的Inner和Outer并且也实现了Borrow,然后在代码里用Inner作为key去调用HashMap::get();那么如果HashMap::get()换了原型后,用户的代码也就编译不过了吧。
--
👇
hangj: 如果仔细看标准库里所有
Borrow
trait 的实现(Implementors),你会发现它们都是impl Borrow<Inner> for Outer
这种模式的,其中 Inner 和 Outer 的关系是这样:这种情况下实现
Borrow<Inner> for Outer
很简单Borrow<str> for String
Borrow<[T]> for Vec<T>
等等都是如此,从 Outer borrow 出它内部的数据而已但是对于没有包含关系的
TmpKey
和Key
, 要实现Borrow<TmpKey> for Key
是几乎不可能做到的--
👇
LongRiver: 虽然难,但应该也是可以做到的吧。我看Borrow的文档里,就举了String和str的例子。
我看SO上的那个回答也说了,非常难实现Borrow。
我的意思是如果这么改,至少在理论上,是不兼容了。实际上可能会影响非常小。
--
👇
hangj: 问题是
Borrow<TmpKey> for Key
如何实现?这几乎是无法做到的。如果可以做到的话,Equavlant
就没必要存在了--
👇
LongRiver: 我说的“原来的代码”,指的是使用了hashmap的app的代码,而不是 hashmap自己的代码。
比如我之前写了个app,使用hashmap,并且实现了
Borrow<TmpKey> for Key
。那么如果HashMap::get()的原型中参数key的约束改了,改成了Equavlant。那么我原来的app代码还能编译通过吗?
我是猜的,感觉就不会通过了。
如果仔细看标准库里所有
Borrow
trait 的实现(Implementors),你会发现它们都是impl Borrow<Inner> for Outer
这种模式的,其中 Inner 和 Outer 的关系是这样:这种情况下实现
Borrow<Inner> for Outer
很简单Borrow<str> for String
Borrow<[T]> for Vec<T>
等等都是如此,从 Outer borrow 出它内部的数据而已但是对于没有包含关系的
TmpKey
和Key
, 要实现Borrow<TmpKey> for Key
是几乎不可能做到的--
👇
LongRiver: 虽然难,但应该也是可以做到的吧。我看Borrow的文档里,就举了String和str的例子。
我看SO上的那个回答也说了,非常难实现Borrow。
我的意思是如果这么改,至少在理论上,是不兼容了。实际上可能会影响非常小。
--
👇
hangj: 问题是
Borrow<TmpKey> for Key
如何实现?这几乎是无法做到的。如果可以做到的话,Equavlant
就没必要存在了--
👇
LongRiver: 我说的“原来的代码”,指的是使用了hashmap的app的代码,而不是 hashmap自己的代码。
比如我之前写了个app,使用hashmap,并且实现了
Borrow<TmpKey> for Key
。那么如果HashMap::get()的原型中参数key的约束改了,改成了Equavlant。那么我原来的app代码还能编译通过吗?
我是猜的,感觉就不会通过了。
虽然难,但应该也是可以做到的吧。我看Borrow的文档里,就举了String和str的例子。
我看SO上的那个回答也说了,非常难实现Borrow。
我的意思是如果这么改,至少在理论上,是不兼容了。实际上可能会影响非常小。
--
👇
hangj: 问题是
Borrow<TmpKey> for Key
如何实现?这几乎是无法做到的。如果可以做到的话,Equavlant
就没必要存在了--
👇
LongRiver: 我说的“原来的代码”,指的是使用了hashmap的app的代码,而不是 hashmap自己的代码。
比如我之前写了个app,使用hashmap,并且实现了
Borrow<TmpKey> for Key
。那么如果HashMap::get()的原型中参数key的约束改了,改成了Equavlant。那么我原来的app代码还能编译通过吗?
我是猜的,感觉就不会通过了。
问题是
Borrow<TmpKey> for Key
如何实现?这几乎是无法做到的。如果可以做到的话,Equavlant
就没必要存在了--
👇
LongRiver: 我说的“原来的代码”,指的是使用了hashmap的app的代码,而不是 hashmap自己的代码。
比如我之前写了个app,使用hashmap,并且实现了
Borrow<TmpKey> for Key
。那么如果HashMap::get()的原型中参数key的约束改了,改成了Equavlant。那么我原来的app代码还能编译通过吗?
我是猜的,感觉就不会通过了。
我说的“原来的代码”,指的是使用了hashmap的app的代码,而不是 hashmap自己的代码。
比如我之前写了个app,使用hashmap,并且实现了
Borrow<TmpKey> for Key
。那么如果HashMap::get()的原型中参数key的约束改了,改成了Equavlant。那么我原来的app代码还能编译通过吗?
我是猜的,感觉就不会通过了。
--
👇
hangj: 原来的代码也没有实现
Borrow<TmpKey> for Key
--
👇
LongRiver: 也没有完全兼容吧。
比如原来的代码里,实现了 Borrow<TmpKey> for Key,然后调用:get(&tmpkey)。
而对于新接口,由于并没有实现 Equavlant<TmpKey> for Key,那么 get(&tmpkey)就会编译不过吧。
--
👇
hangj: 新的
get()
放宽了约束的限制,可以兼容原 API原来的代码也没有实现
Borrow<TmpKey> for Key
--
👇
LongRiver: 也没有完全兼容吧。
比如原来的代码里,实现了 Borrow<TmpKey> for Key,然后调用:get(&tmpkey)。
而对于新接口,由于并没有实现 Equavlant<TmpKey> for Key,那么 get(&tmpkey)就会编译不过吧。
--
👇
hangj: 新的
get()
放宽了约束的限制,可以兼容原 API也没有完全兼容吧。
比如原来的代码里,实现了 Borrow<TmpKey> for Key,然后调用:get(&tmpkey)。
而对于新接口,由于并没有实现 Equavlant<TmpKey> for Key,那么 get(&tmpkey)就会编译不过吧。
--
👇
hangj: 新的
get()
放宽了约束的限制,可以兼容原 API新的
get()
放宽了约束的限制,可以兼容原 API问题是约束放得有点太宽(原约束是 &Key, 现在是 Equivalent trait)了, 而
TmpKey
是我们手工构造的,一旦原始 Key 有什么变化而 TmpKey 忘记同步修改,那就必然出问题,此时这个约束并不会给我们提示所以说这个
get()
好用,但并不完美。如果我们要用 TmpKey 来传参则需要非常谨慎。那么,能否在定义 Key 的时候,通过宏自动定义一个引用版本的 RefKey 呢,比如
自然是可以的,用 macro 不就行嘛,于是我手撸一个
equivalent_ref_struct
macro用法:
playground
--
👇
LongRiver: 明白了。
这种修改get()里key的约束的做法,算是修改了API的定义,属于不兼容的修改了吧?
如果是不兼容的,那感觉标准库是不会改的。除非新增一套API,比如get_equiv()之类的?那也要加好多API了。
明白了。
这种修改get()里key的约束的做法,算是修改了API的定义,属于不兼容的修改了吧?
如果是不兼容的,那感觉标准库是不会改的。除非新增一套API,比如get_equiv()之类的?那也要加好多API了。
--
👇
hangj:
hashbrown
里面添加Equivalent trait
的时间是 2022-7-22hashbrown
里面添加Equivalent trait
的时间是 2022-7-22https://github.com/rust-lang/hashbrown/commit/75a9ef979b7502ce123631d5af77b65ac6131911#diff-110e61eb735b79c762510f02f157a12094bcf2b9a15d2ed767532a0e3d5f7bafR1214
标准库里一直还没跟进,我觉得可能标准库更严谨吧。
你看这个
get
函数它要求Q: Hash + Equivalent<K>
, 但其实它还有一个隐含的要求,就是对于具有相同 fields 的Q
和K
, 它们的 hash 值应该是相等的。我之前的例子中Key
和TmpKey
就符合这个要求当 id 和 name 都相等时,输出的这两个 hash 也是相等的。
但是,万一有人这么定义
TmpKey
:那么我们依然可以把
&TmpKey
传递给get()
, 但是可能永远得不到我们想要的结果。。因此,我觉得这个接口可能还不适合完全暴露出来吧。
SO 上那个回复没有上下文,我也不太清楚是啥意思。
--
👇
LongRiver: 晚点有空了学习下。
我看在SO上你的这个答案下面,有人回复 “Now that is a promising new feature.”,这是什么意思?是说标准库已经在准备这个功能了吗?
晚点有空了学习下。
我看在SO上你的这个答案下面,有人回复 “Now that is a promising new feature.”,这是什么意思?是说标准库已经在准备这个功能了吗?
--
👇
hangj: 研究了下,发现还有更简单写法:
多谢回复。
这确实是个方法。不过这应该是增加了一些开销。就要看具体场景中是否值得了。
--
👇
asuper: 以下代码经实测,应该能满足你的需求,原理就是让Key可以同时接受String和&str,并让他们可以相等
你的问题是,为什么是
&Q
而不是Q
?如果是这个问题的话,那我理解get没必要consume这个key,所以引用就足够了。
--
👇
leolee0101: 不好意思,稍微歪个楼。 std hashmap 的 get 方法为什么设计为 接受 引用类型的key 呢?
fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
不好意思,稍微歪个楼。 std hashmap 的 get 方法为什么设计为 接受 引用类型的key 呢?
fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
以下代码经实测,应该能满足你的需求,原理就是让Key可以同时接受String和&str,并让他们可以相等
研究了下,发现还有更简单写法: