在Trait函数中设置生命周期出现问题

3

我有一个特质 Atom,它有许多关联类型,其中之一是拥有版本OP,另一个是基本相同数据的借用版本O。我有一个函数to_pow_view从所拥有的版本创建视图,并且我有一个等式运算符。

以下是一个尝试:

pub trait Atom: PartialEq {
    // variants truncated for this example
    type P<'a>: Pow<'a, R = Self>;
    type OP: OwnedPow<R = Self>;
}

pub trait Pow<'a>: Clone + PartialEq {
    type R: Atom;
}

#[derive(Debug, Copy, Clone)]
pub enum AtomView<'a, R: Atom> {
    Pow(R::P<'a>),
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum OwnedAtom<R: Atom> {
    Pow(R::OP),
}

pub trait OwnedPow {
    type R: Atom;

    fn some_mutable_fn(&mut self);

    fn to_pow_view<'a>(&'a self) -> <Self::R as Atom>::P<'a>;

    // compiler said I should add 'a: 'b
    fn test<'a: 'b, 'b>(&'a mut self, other: <Self::R as Atom>::P<'b>) {
        if self.to_pow_view().eq(&other) {
            self.some_mutable_fn();
        }
    }
}

impl<R: Atom> OwnedAtom<R> {
    // compiler said I should add 'a: 'b, why?
    pub fn eq<'a: 'b, 'b>(&'a self, other: AtomView<'b, R>) -> bool {
        let a: AtomView<'_, R> = match self {
            OwnedAtom::Pow(p) => {
                let pp = p.to_pow_view();
                AtomView::Pow(pp)
            }
        };

        match (&a, &other) {
            (AtomView::Pow(l0), AtomView::Pow(r0)) => l0 == r0,
        }
    }
}

// implementation

#[derive(Debug, Copy, Clone, PartialEq)]
struct Rep {}

impl Atom for Rep {
    type P<'a> = PowD<'a>;
    type OP = OwnedPowD;
}

#[derive(Debug, Copy, Clone, PartialEq)]
struct PowD<'a> {
    data: &'a [u8],
}

impl<'a> Pow<'a> for PowD<'a> {
    type R = Rep;
}

struct OwnedPowD {
    data: Vec<u8>,
}

impl OwnedPow for OwnedPowD {
    type R = Rep;

    fn some_mutable_fn(&mut self) {
        todo!()
    }

    fn to_pow_view<'a>(&'a self) -> <Self::R as Atom>::P<'a> {
        PowD { data: &self.data }
    }
}

fn main() {}

这段代码报错:

27 |     fn test<'a: 'b, 'b>(&'a mut self, other: <Self::R as Atom>::P<'b>) {
   |                     -- lifetime `'b` defined here
28 |         if self.to_pow_view().eq(&other) {
   |            ------------------
   |            |
   |            immutable borrow occurs here
   |            argument requires that `*self` is borrowed for `'b`
29 |             self.some_mutable_fn();
   |             ^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

我希望这段代码能够正常工作,因为不可变的borrow应该会在eq函数评估后被释放。

在等式函数eq中,有些生命周期设置是错误的:我认为'a'b之间没有关系;它们只需要活得足够长来进行比较。然而,编译器告诉我应该添加'a: 'b,我不理解为什么要这样做。在test函数中也发生了同样的事情。

这些问题让我认为to_pow_view中的生命周期有问题,但我尝试的任何修改都没有使它正常工作(除了删除&'a self中的'a生命周期,但这样做会导致OwnedPowD无法编译)。

链接到代码演示

有人可以帮忙理解发生了什么吗?

1个回答

3

重点在于:你将Pow约束为PartialEq。但是,PartialEqPartialEq<Self>。换句话说,Pow<'a>只实现了对于相同的'aPartialEq<Pow<'a>>

通常对于任何带有生命周期和PartialEq的类型都是如此,那么为什么它总是有效而在这里却无效呢?

它通常有效,因为如果我们比较T<'a>==T<'b>,编译器可以将生命周期缩短到两者中的最短生命周期,并进行比较。

然而,Pow是一个trait。Trait相对于其生命周期是不变的,换句话说,它必须保持完全不变,既不能更长也不能更短。这是因为它们可能与不变类型一起使用,例如Cell<&'a i32>。以下是如果允许这样做,它如何被利用的示例:

use std::cell::Cell;

struct Evil<'a> {
    v: Cell<&'a i32>,
}

impl PartialEq for Evil<'_> {
    fn eq(&self, other: &Self) -> bool {
        // We asserted the lifetimes are the same, so we can do that.
        self.v.set(other.v.get());
        false
    }
}

fn main() {
    let foo = Evil { v: Cell::new(&123) };
    {
        let has_short_lifetime = 456;
        _ = foo == Evil { v: Cell::new(&has_short_lifetime) };
    }
    // Now `foo` contains a dangling reference to `has_short_lifetime`!
    dbg!(foo.v.get());
}

以上代码无法编译,因为Evil'a上不变,但如果它是可变的,那么在安全代码中会出现未定义行为。因此,可能包含像Evil这样的类型的traits也在其生命周期上不变。
因此,编译器无法缩小other的生命周期。它可以缩短self.to_pow_view()(在test()中,eq()类似),因为它并没有真正缩小它,只是为to_pow_view()self选择了更短的生命周期。但由于PartialEq仅适用于具有相同生命周期的类型,这意味着由self.to_pow_view()产生的Pow必须具有与other相同的生命周期。因此,(a)'a必须大于或等于'b,所以我们可以从中选择'b,(b)通过比较,我们借用self可能拥有整个'a,因为可能存在'a == 'b,因此比较对于'a仍然是不可变借用而我们需要可变借用它来执行some_mutable_fn()
一旦我们理解了问题,就可以考虑解决方案。要么我们要求Pow'a上是协变的(可以缩小),要么我们要求它实现PartialEq<Pow<'b>>以适用于任何生命周期'b。第一个在Rust中不可能,但第二个是可能的。
pub trait Pow<'a>: Clone + for<'b> PartialEq<<Self::R as Atom>::P<'b>> {
    type R: Atom;
}

这会触发一个错误,因为自动生成的PartialEq不满足该要求:

error: implementation of `PartialEq` is not general enough
  --> src/main.rs:73:10
   |
73 | impl<'a> Pow<'a> for PowD<'a> {
   |          ^^^^^^^ implementation of `PartialEq` is not general enough
   |
   = note: `PartialEq<PowD<'0>>` would have to be implemented for the type `PowD<'a>`, for any lifetime `'0`...
   = note: ...but `PartialEq` is actually implemented for the type `PowD<'1>`, for some specific lifetime `'1`

因此我们需要手动实现 PartialEq:

impl<'a, 'b> PartialEq<PowD<'b>> for PowD<'a> {
    fn eq(&self, other: &PowD<'b>) -> bool {
        self.data == other.data
    }
}

现在它可以工作了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接