生命周期中捕获的引用
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);
}
}
在我的项目(人工简化)中,我有一个结构 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);
}
}