rust 在特性函数中设置寿命的问题

oxosxuxt  于 2023-01-21  发布在  其他
关注(0)|答案(1)|浏览(159)

我有一个trait 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

我希望这能起作用,因为在eq函数求值之后,应该立即删除不可变的借位。
在这段代码中,生存期的设置有错误,已经在等式函数eq中:我期望'a'b之间没有关系;他们应该活得足够长来做比较。但是,编译器告诉我应该添加'a: 'b,我不明白为什么。同样的事情发生在函数test上。
这些问题使我相信to_pow_view中的生存期是错误的,但是我尝试的任何修改都没有使它工作(除了删除&'a self上的'a生存期,但是OwnedPowD不再编译)。
Link to playground
有人能帮我弄清楚到底发生了什么吗?

omhiaaxx

omhiaaxx1#

重点是:您将Pow约束为PartialEq。但是,PartialEqPartialEq<Self>。换句话说,**Pow<'a>仅为相同的'a**实现PartialEq<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是不变的,但是如果它是不变的,那么它在安全代码中会包含UB,因此,可能包含Evil类型的trait在它们的生命周期中也是不变的。
因此,编译器不能缩短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,因此比较借用self来代替'a,所以当我们对some_mutable_fn()可变地借用它时,它仍然是不可变地借用的。
一旦我们理解了这个问题,我们就可以考虑解决方案了,要么我们要求Pow'a上协变(可以收缩),要么我们要求它在任何生存期'b上实现PartialEq<Pow<'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
    }
}

现在它起作用了。

相关问题