具有关联类型的泛型参数的生命周期问题

Lifetime issue with generic parameter with associated type

我遇到一个问题,编译器说数据被多次可变借用。我在循环中借用数据,但是借用仅在循环中局部使用,因此下一次迭代借用应该已经完成​​。

(对我来说)令人困惑的是,它适用于局部变量,但当借用通过特征中定义的方法发生时则无效,并且该变量作为通用类型参数的可变引用传递这与该特征有关。

以下代码是一个简化的示例,但显示了完全相同的错误:

trait GetElem<'a, T> {
    type ElemRef: Deref<Target=T>;

    fn get_at(&'a mut self, index: usize) -> Self::ElemRef;
}

struct Ref<'a, T>(&'a T);

impl<'a, T> Deref for Ref<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl<'a, T: 'a> GetElem<'a, T> for Vec<T> {
    type ElemRef = Ref<'a, T>;

    fn get_at(&'a mut self, index: usize) -> Self::ElemRef {
        Ref(self.get(index).unwrap())
    }
}

fn test<'a, C: 'a + GetElem<'a, i32>>(passed_data: &'a mut C) {
    let mut local_data = vec![1, 2];

    let x = local_data.get_at(0);
    let tmp = *x;
    let y = local_data.get_at(1);
    let _ = tmp + *y;

    let x = passed_data.get_at(0);
    let tmp = *x;
    let y = passed_data.get_at(1);
    let _ = tmp + *y;
}

或在 playground

上查看

所以,在这个例子中,我有一个 passed_data(在对实现 GetElem<i32> 特征的类型的可变引用后面)和一个 local_data,这是一个Vec<i32>.

对于两者,我尝试做同样的事情:获取第一个元素的可变引用,将值复制到临时变量,获取第二个值的可变引用,然后计算两者的和。

这对 local_data 有效,但对 passed_data 无效,并出现以下错误:

error[E0499]: cannot borrow `*passed_data` as mutable more than once at a time

.

你告诉编译器“我借用 self,只要 'a,我就可以保持引用存活”。并且 'a 是在结构(或特征)中定义的,这意味着您只能使用该结构一次。

你不想这样。你想说,“我借用 self,我 return 一个与这个借用相关的类型”。如果这是一个参考,你就不会有问题。它是 fn get_at<'a>(&'a mut self) -> &'a T(这是另一个反模式,顺便说一句,将 &mut 降级为 &。但有时你会想要那样)。或者只是 fn get_at(&mut self) -> &T,使用生命周期省略。如果这是一个结构,例如 Ref<'a, T>,您也不会有问题 - 只是 return fn get_mut(&mut self) -> Ref<'_, T>(或没有省略 fn get_mut<'a>(&'a mut self) -> Ref<'a, T>)。但这不是其中之一. 这是关联类型。

您希望关联类型在整个生命周期内都是通用的。这称为 GAT(通用关联类型)。不幸的是,在稳定的 Rust 中是不可能的。它 可以用 nightly:

#![feature(generic_associated_types)]

use std::ops::Deref;

trait GetElem<T> {
    type ElemRef<'a>: Deref<Target = T> + 'a
    where
        Self: 'a;

    fn get_at(&mut self, index: usize) -> Self::ElemRef<'_>;
}

struct Ref<'a, T>(&'a T);

impl<T> Deref for Ref<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl<T> GetElem<T> for Vec<T> {
    type ElemRef<'a>
    where
        T: 'a,
    = Ref<'a, T>;

    fn get_at(&mut self, index: usize) -> Self::ElemRef<'_> {
        Ref(self.get(index).unwrap())
    }
}

Playground.

下面是你如何使用它:

fn test<C: GetElem<i32>>(passed_data: &mut C) {
    let mut local_data = vec![1, 2];

    let x = local_data.get_at(0);
    let tmp = *x;
    let y = local_data.get_at(1);
    let _ = tmp + *y;

    let x = passed_data.get_at(0);
    let tmp = *x;
    drop(x);
    let y = passed_data.get_at(1);
    let _ = tmp + *y;
}

您可能想知道为什么我需要 drop()x 的新范围也可以,但我更喜欢这种形式)。好吧,我没有理由不需要。 Pre-NLL (Non-Lexical Lifetimes),你也需要它作为引用——引用落在范围的末尾,只有这样借用才是免费的。但是,NLL 改进了这一点,因为如果我不再 使用 引用 - 没有理由不将其视为已删除。然而,对于结构,这不是真的:一个结构可以实现 Drop 并观察那里的引用,编译器不能更早地删除它,因为删除顺序是有保证的。