我正在尝试使用GAT来增强内存数据存储的API。数据以值的形式组织,其中每个值都包含一个查找键。您可以将其视为数据库表中的一行,其中整行都是一个“值”,但它还包含一个或多个主键列。
我们的想法是用一个特征来描述它,这样你就可以通过提供键来查找一个特定的值,键必须能够引用值,这样如果值的键部分是String
,你就可以用&str
来查找它,这就是GAT的作用:
pub trait Value {
type Key<'a>: PartialEq where Self: 'a;
fn as_key<'a>(&'a self) -> Self::Key<'a>;
}
Key<'a>
GAT提供了一个生存期,as_key()
可以用它来返回一个引用内部数据的值。注意,as_key()
不能只返回一个对键的引用,因为返回的键可能是Self
中不存在的东西,比如复合键。例如,这些都是可能的:
struct Data {
s: String,
n: u64,
// ... more fields ...
}
// example 1: expose key as self.s as a &str key
impl Value for Data {
type Key<'a> = &'a str;
fn as_key(&self) -> &str { &self.s }
}
// example 2: expose key as a pair of (self.s.as_str(), self.n)
impl Value for Data {
type Key<'a> = (&'a str, u64);
fn as_key(&self) -> (&str, u64) { (&self.s, self.n) }
}
使用此特征的泛型代码示例如下所示:
pub struct Table<T> {
data: Vec<T>,
}
impl<T: Value> Table<T> {
fn find<'a: 'k, 'k>(&'a self, k: T::Key<'k>) -> Option<usize> {
self.data.iter().position(|v| v.as_key() == k)
}
}
这非常好用,您可以在in the playground环境下使用它(一个更实际的例子是需要Ord
或Hash
来构建一个更复杂的存储,但这足以说明这个想法)。
现在,让我们做一个简单的更改,将表数据存储在一个Mutex
中。代码看起来几乎相同,并且由于它只返回 position,互斥体操作应该保持在实现内部:
struct Table<T> {
data: Mutex<Vec<T>>,
}
impl<T: Value> Table<T> {
pub fn find<'a: 'k, 'k>(&'a self, k: T::Key<'k>) -> Option<usize> {
let data = self.data.lock().unwrap();
data.iter().position(|v| v.as_key() == k)
}
}
但是,上面的代码不能编译--它抱怨“数据的生存时间不够长”:
error[E0597]: `data` does not live long enough
--> src/main.rs:18:9
|
16 | pub fn find<'a: 'k, 'k>(&'a self, k: T::Key<'k>) -> Option<usize> {
| -- lifetime `'k` defined here
17 | let data = self.data.lock().unwrap();
18 | data.iter().position(|v| v.as_key() == k)
| ^^^^^^^^^^^ ---------- argument requires that `data` is borrowed for `'k`
| |
| borrowed value does not live long enough
19 | }
| - `data` dropped here while still borrowed
Playground
我不太明白这个错误-为什么data
需要在我们要比较的密钥的生命周期内生存?我尝试:
- 改变生存期,使得
'k
和'a
的生存期完全去耦 - 将比较结果提取到一个简单的函数中,该函数接收
&'a T
和&T::Key<'b>
,并在比较它们之后返回bool
(并且该函数自己编译) - 将
Iterator::position()
替换为显式for
循环
但是没有任何帮助,错误总是以某种形式存在。注意,将v.as_key()
和k
放在同一个闭包中是完全法律的的(例如like this),只有当你试图比较它们时才会出现错误。
我对这个问题的直观理解是,与Value::Key<'a>
相关的Eq
边界只适用于另一个Key<'a>
。
是否可以通过修改as_key()
接口的生命周期来解决此问题?这是否是此处所述问题的变体?
EDIT:按照kmdreko的建议放宽PartialEq
到HRTB for<'b> PartialEq<Self::Key<'b>>
的绑定可以修复上面的示例,但会中断泛型。例如,Value
的以下实现无法编译:
struct NoKey<T>(T);
impl<T> Value for NoKey<T> {
type Key<'a> = () where T: 'a;
fn as_key(&self) -> () {
()
}
}
错误为:
error[E0311]: the parameter type `T` may not live long enough
--> src/lib.rs:38:20
|
38 | type Key<'a> = () where T: 'a;
| ^^ ...so that the type `NoKey<T>` will meet its required lifetime bounds
1条答案
按热度按时间qv7cva1a1#
我对这个问题的直观理解是,与
Value::Key<'a>
相关的Eq
界限只适用于另一个Key<'a>
。这是正确的,你可以通过使用一个更高等级的trait来放宽这个限制,要求
Key<'a>
与所有Key<'b>
都是可比较的:我不认为有任何其他的方法,因为大多数类型是协变的,关于它们的相关生存期,但是对于
T::Key<'k>
,我不认为你可以限制'k
可以被缩短。在编辑后的问题中指出的泛型问题可以通过要求泛型为
'static
(playground)来解决。注意'static
界限仅适用于作为整体的值,键仍然可以引用值的一部分。