生命周期中捕获的引用

Captured references in lifetimes

在我的项目(人工简化)中,我有一个结构 ContainsData,它只能通过句柄 HoldsRef<'a> 进行修改,它包含对 [=14= 中某个字段的引用].另外,使用句柄后,必须调用一些cleanup方法。

struct ContainsData(i64);
impl ContainsData {
    fn cleanup(&mut self) {
        self.0 = self.0.abs();
    }
}

struct HoldsRef<'a>(&'a mut i64);
impl<'a> HoldsRef<'a> {
    fn set(&mut self, value: &'a i64) {
        *self.0 += value; 
    }
}

// A typical interaction with "ContainsData"
fn main() {
    let mut container = ContainsData(5);
    let x = 32;

    let mut handle = HoldsRef(&mut container.0);     // _
    handle.set(&x);                                  // |  A    
    handle.set(&31);                                 // |
    container.cleanup()                              // v
}

我想用对 ContainsData 的单个方法调用替换 A。所有对句柄的修改都将在闭包的上下文中完成,如下所示:

impl ContainsData {
    fn modify(&mut self, continuation : impl Fn(HoldsRef)) {
        let handle = HoldsRef(&mut self.0); 
        continuation(handle);
        self.cleanup()
    }
}

fn main2() {
    let mut container = ContainsData(5);
    let x = 32;

    container.modify(|mut handle| {
        handle.set(&x);
        handle.set(&32);
    });
}

这不编译:

error[E0597]: `x` does not live long enough
  --> src/main.rs:56:21
   |
55 |     container.modify(|mut handle| {
   |                      ------------ value captured here
56 |         handle.set(&x);
   |         ------------^-
   |         |           |
   |         |           borrowed value does not live long enough
   |         argument requires that `x` is borrowed for `'static`
...
59 | }
   | - `x` dropped here while still borrowed

第一个问题:为什么 Rust 认为 x 是为 'static 借来的?我认为它与 Fn(HoldsRef) 的省略规则有关,但我不太明白在那种情况下规则说的是什么。

第二个问题:有没有办法更改 modify 的签名或稍微更改代码以便能够用单个方法调用替换 A ?我尝试过各种注释生命周期都无济于事。

fn modify<'a>(&mut self, continuation : impl Fn(HoldsRef<'a>))
// cannot infer an appropriate lifetime (b/c, I guess, the handle has a lifetime strictly smaller than the function)

更广泛的上下文: HoldsRef<'a> 实际上是 wgpu::RenderPass<'a>。我没有更改方法签名的选项 set.

编辑:在下面找到了部分解决方案。


让我们了解这里发生了什么。

闭包中的生命周期(impl Fn(&T),还有 impl Fn(T<'_>),和你的一样impl Fn(HoldsRef)),不要 desugar into通用生命周期,如正常的省略生命周期(其中 fn foo(_: &T) 脱糖为 fn foo<'a>(_: &'a T));相反,它们被脱糖为 higher ranked trait bounds.

impl Fn(&T)脱糖成impl for<'a> Fn(&'a T),同理,impl Fn(HoldsRef)脱糖成impl for<'a> Fn(HoldsRef<'a>)。另见 .

HRTB(Higher-Ranked Traits Bounds)意味着生命周期可以是any生命周期。而“任何一生”当然包括'static。因为 'static 是可能的最长生命周期,所以编译器使用它就好像你写了 impl Fn(HoldsRef<'static>) 一样。注意 这仅从回调的角度来看是正确的,也就是说,回调必须能够处理这种情况,但 modify() 可以在任何它想要的生命周期内调用它,而不是仅静态(与指定 impl Fn(HoldsRef<'static>) 时发生的情况相反,您必须传递 HoldsRef<'static>)。

因为HoldsRef::set()方法需要&'a i64,而'a'static,所以就好像需要&'static i64。现在你就会明白为什么当你提供较短的生命周期时编译器会报错。

您可能认为解决方案只是采用通用生命周期而不是 HRTB,并将其绑定到 self:

fn modify<'a>(&'a mut self, continuation: impl Fn(HoldsRef<'a>)) {

然而它不起作用:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:19:9
   |
16 |     fn modify<'a>(&'a mut self, continuation: impl Fn(HoldsRef<'a>)) {
   |               -- lifetime `'a` defined here
17 |         let handle = HoldsRef(&mut self.0);
   |                               ----------- first mutable borrow occurs here
18 |         continuation(handle);
   |         -------------------- argument requires that `self.0` is borrowed for `'a`
19 |         self.cleanup();
   |         ^^^^^^^^^^^^^^ second mutable borrow occurs here

这是有道理的:因为我们告诉编译器回调需要一个 HoldsRef<'a>,当我们 cleanup() 时它可以存活;因为这个 HoldsRef 借用了 self.0,我们在清理时仍然持有对 self.0 的可变引用。


编辑: 我找到了解决办法!这不是问题的直接解决方案,而是某种解决方法。

您可以 impl Drop for HoldsRef,并在此实现中调用 cleanup()。这意味着您必须持有对整个 ContainsData 的引用,而不仅仅是它的值。喜欢:

impl ContainsData {
    fn modify<'a>(&'a mut self, continuation: impl Fn(HoldsRef<'a>)) {
        let handle = HoldsRef(self);
        continuation(handle);
    }
}

struct HoldsRef<'a>(&'a mut ContainsData);

impl<'a> HoldsRef<'a> {
    fn set(&mut self, value: &'a i64) {
        self.0.0 += value;
    }
}

impl Drop for HoldsRef<'_> {
    fn drop(&mut self) {
        self.0.cleanup();
    }
}

这也允许您使用 RAII 风格而不是回调风格,后者通常被认为是更惯用的 Rust:

impl ContainsData {
    fn get_handle(&mut self) -> HoldsRef<'_> {
        HoldsRef(self)
    }
}

fn main() {
    let mut container = ContainsData(5);
    let x = 32;

    // A block just to make dropping explicit.
    {
        let mut handle = container.get_handle();
        handle.set(&x);
        handle.set(&32);
    }
}