rust 与GAT相关的生存期与互斥临时生存期冲突

pkmbmrz7  于 2022-11-24  发布在  其他
关注(0)|答案(1)|浏览(259)

我正在尝试使用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环境下使用它(一个更实际的例子是需要OrdHash来构建一个更复杂的存储,但这足以说明这个想法)。
现在,让我们做一个简单的更改,将表数据存储在一个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

Playground

qv7cva1a

qv7cva1a1#

我对这个问题的直观理解是,与Value::Key<'a>相关的Eq界限只适用于另一个Key<'a>
这是正确的,你可以通过使用一个更高等级的trait来放宽这个限制,要求Key<'a>与所有Key<'b>都是可比较的:

pub trait Value {
    type Key<'a>: for<'b> PartialEq<Self::Key<'b>> // <-----
    where
        Self: 'a;

    fn as_key<'a>(&'a self) -> Self::Key<'a>;
}

我不认为有任何其他的方法,因为大多数类型是协变的,关于它们的相关生存期,但是对于T::Key<'k>,我不认为你可以限制'k可以被缩短。
在编辑后的问题中指出的泛型问题可以通过要求泛型为'staticplayground)来解决。注意'static界限仅适用于作为整体的值,键仍然可以引用值的一部分。

相关问题