< 返回版块

Matheritasiv 发表于 2024-01-14 14:49

Tags:生命周期, dropck

下面这段代码

    let calc_ctx = Bump::new();
    let mut it_null = std::iter::empty();
    let ds = Digits::new(&calc_ctx, &mut it_null);
    let mut it = ds.iter();
//               ^^ borrowed value does not live long enough
    let ds2 = Digits::new(&calc_ctx, &mut it);
    let _ = &ds + &ds2;

编译时会报错,说在dsdrop过程中可能会使用指出的那处借用。我自己把代码简化成这样,但并没有报错,有没有大佬帮忙分析一下?

相关的定义列在下面:

pub struct Digits<'a, 'b>(
    &'a RefCell<DigitsDatum<'a>>,
    DigitsGc<'a>,                    // 实现了`Drop`,对于`'a`协变
    PhantomData<&'b &'a ()>,
);
struct DigitsDatum<'a> {
    stream: &'a mut dyn Iterator<Item=i32>,
    digits: BVec<'a, i32>,           // `BVec`是`bumpalo::collections::Vec`,对于`'a`协变
}

impl<'a, 'b> Digits<'a, 'b> {
    fn new(bump: &'a Bump, stream: &'a mut dyn Iterator<Item=i32>) -> Self;
    pub fn iter(&'b self) -> DigitsStream<'a, 'b>;
}
unsafe impl<#[may_dangle] 'a, #[may_dangle] 'b> Drop for Digits<'a, 'b> {
    fn drop(&mut self) {} // 空实现
}

pub struct DigitsStream<'a, 'b> {
    datum: &'b RefCell<DigitsDatum<'a>>,
    index: usize,
}

fn add<'a1, 'a2: 'a1, 'b>(self: &'b Digits<'a1, 'b>, other: &'b Digits<'a2, 'b>) -> Digits<'a1, 'b>;

评论区

写评论
作者 Matheritasiv 2024-01-19 16:43

最终的解决方案是把数据结构设计成这样:

pub struct Digits<'m, 'a, 'b>(
    NonNull<DigitsDatum<'a>>,
    DigitsGc<'m, 'a>,
    PhantomData<&'b &'a &'m ()>,
);
struct DigitsDatum<'a> {
    stream: DigitsIter<'a>,
    digits: BVec<'a, i32>,
}
struct DigitsIter<'a>(NonNull<dyn Iterator<Item=i32> + 'a>);
pub struct DigitsStream<'a, 'b> {
    datum: NonNull<DigitsDatum<'a>>,
    index: usize,
    _lifetime: PhantomData<&'b &'a ()>,
}

这里所有的生命周期都是协变的,需要内部可变性时用unsafe操作从NonNull获取可变引用。

苦瓜小仔 2024-01-15 22:37

op 的标注和永久借用造成的 drop 检查失败

fn main() {
    let mem_pool = Bump::new();
    let mut it_null = std::iter::empty();
    let d1 = Digits::new(&mem_pool, &mut it_null); // Digits::<'0, '1>::new(&'0 mem_pool, &'0 mut dyn ...), '0: '1
    let mut it = d1.iter(); // Digits::iter(&'1 Self) -> DigitsStream<'0, '1>
    let d2 = Digits::new(&mem_pool, &mut it); // Digits::<'3, '4>::new(&'3 mem_pool, &'3 mut dyn), '3: '4
                                              // &'3 mut DigitsStream<'0, '1>,且 '0: '3,'1: '3, '0: '4, '1: '4
    op(&d1, &d2); // op(&'b Digits<'0, '1>, &'b Digits<'3, '4>)
    // ① 'b = '1,但 '1: '4,'4 无法逆变(延长到 '1),所以这条路不行
    // ② 'b = '4,'1 协变成 '4(也就是 '1 == '4),有 op(&'4 Digits<'0, '4>, &'4 Digits<'3, '4>),
    //   检查 '3: '0,这可能吗?由于 '0 和 '3 是不变的,并且 '0: '3,要满足 '3: '0,必须 '3 == '0,
    //   对于 &'0 mem_pool 和 &'3 mem_pool,共享引用可以有相同的生命周期,
    //   但对于 &'3 mut DigitsStream<'0, '1>,这意味着 &'0 mut DigitsStream<'0, '1>,以及 '1: '0(通过 '1: '3),
    //   由于 '0: '1,所以 '0 == '1,那么 '0 == '1 == '3 == '4,且都不变,
    //   d1 = Digits::<'0, '0>::new(&'0 mem_pool, &'0 mut dyn ...)
    //   it = Digits::<'0, '0>::iter(&'0 Self) -> DigitsStream<'0, '0>
    //   d2 = Digits::<'0, '0>::new(&'0 mem_pool, '0 mut DigitsStream<'0, '0>)
    //   op(&'0 Digits<'0, '0>, &'0 Digits<'0, 0>)
    // 显然 Digits 被永久借用成 '0,而它其中的 BVec 实现的 Drop 需要 &'a_new mut BVec,
    // 永久借用无法出让除自己之外的任何 'a_new,所以无法通过 drop 检查,编译失败。
}
// 至此可以回答“为什么我自己写的 Bump 和 BVec 可以通过编译,使用 bumpalo 的不行”:
// 因为你写的 BVec 虽然内部需要 drop Vec 之类的,但其自身不实现 Drop trait,不需要 &'a_new BVec。
// 如果你自己实现 BVec,那么和使用 bumpalo 一样,最终编译失败:
impl<T> Drop for BVec<'_, T> {
    fn drop(&mut self) {}
}
作者 Matheritasiv 2024-01-15 15:58

我提取出了能够复现问题的代码片段,这里的BumpBVec相关的函数类型标记是照着库文档写的,奇怪的是使用这里的演示版本并没有编译错误,但使用bumpalo库就会出问题。

苦瓜小仔 2024-01-14 16:43

对一个复杂的生命周期问题能复现到多精简,对生命周期的理解就多深入。

没有可复现的代码,所以无法解释你遇到什么问题。

我只看到一个 &'a RefCell<DigitsDatum<'a>> 永久借用,那么它会出现 borrowed value does not live long enough,完全是我意料之中的事情。(没有说这一定是导致你代码出现问题的地方,生命周期标注可能是另一个源头:)

1 共 4 条评论, 1 页