我和借阅检查员打架。我有两个相似的代码片段,一个像我期望的那样工作,另一个没有。
符合我期望的那个:
mod case1 {
struct Foo {}
struct Bar1 {
x: Foo,
}
impl Bar1 {
fn f<'a>(&'a mut self) -> &'a Foo {
&self.x
}
}
// only for example
fn f1() {
let mut bar = Bar1 { x: Foo {} };
let y = bar.f(); // (1) 'bar' is borrowed by 'y'
let z = bar.f(); // error (as expected) : cannot borrow `bar` as mutable more
// than once at a time [E0499]
}
fn f2() {
let mut bar = Bar1 { x: Foo {} };
bar.f(); // (2) 'bar' is not borrowed after the call
let z = bar.f(); // ok (as expected)
}
}
没有:
mod case2 {
struct Foo {}
struct Bar2<'b> {
x: &'b Foo,
}
impl<'b> Bar2<'b> {
fn f(&'b mut self) -> &'b Foo {
self.x
}
}
fn f4() {
let foo = Foo {};
let mut bar2 = Bar2 { x: &foo };
bar2.f(); // (3) 'bar2' is borrowed as mutable, but who borrowed it?
let z = bar2.f(); // error: cannot borrow `bar2` as mutable more than once at a time [E0499]
}
}
我希望我可以在不刺激编译器的情况下两次调用Bar2::f
,如案例1所示。
问题在评论(3)中:谁借了bar2
,而没有做作?
以下是我的理解:
在案例1中,f2
调用:生命周期参数'a
是接收&Foo
值之一,因此当没有做作时,此生命周期为空,并且bar
来电后,我不会借用Bar1::f
在案例2中,bar2
借用foo
(作为不可变),因此'b
结构中的生命周期参数Bar2
是foo
引用终身,终结于f4
身体的末尾。致电Bar2::f
借用bar2
生命周期,即f4
结尾。
但问题仍然是:谁借了bar2
?可能是Bar2::f
吗?电话结束后Bar2::f
如何保留借来的所有权?我在这里缺少什么?
我在x86_64-pc-windows-msvc上使用Rust 1.14.0-nightly(86affcdf6 2016-09-28)。
答案 0 :(得分:9)
如果是#2,你有这个:
impl<'b> Bar2<'b> {
fn f(&'b mut self) -> &'b Foo {
self.x
}
}
要突出显示:&'b mut self
和&'b Foo
具有相同的生命周期。
这就是说self
的引用和对Foo
实例的返回引用都具有相同的生命周期。查看呼叫站点,您有:
let foo = Foo {};
let mut bar2 = Bar2 { x: &foo };
因此,编译器推断foo
和bar2
具有相同的生命周期。 foo
的生命周期是f4
函数的范围,因此bar2
的可变引用与此共享。
解决此问题的一种方法是删除self
引用的显式生命周期:
fn f(&mut self) -> &'b Foo
这编译并且编译器正确理解对bar2
的引用和对foo
的引用具有不同的生命周期。
游乐场:https://play.rust-lang.org/?gist=caf262dd628cf14cc2884a3af842276a&version=stable&backtrace=0
TLDR:是的,在自引用和返回的引用上具有相同的生命周期说明符意味着f4
的整个范围持有bar2
的可变借位。
答案 1 :(得分:9)
啊......你基本上是自己借来的。
问题取决于您在'b
的生命周期和Foo
的生命周期内使用相同的生命周期(Bar
)。然后编译器尽职地统一这些生命周期,最后你会陷入一种奇怪的情况,在这种情况下,应该在语句结束时结束的借用的生命周期在值超出范围之后结束。
根据经验法则:总是为self
使用新的生命周期。其他任何事情都很奇怪。
值得注意的是,这种模式实际上可能很有用(尽管更可能使用不可变借用):它允许将值锚定到堆栈帧,从而阻止任何移动调用函数,它(有时)用于表示Rust没有很好建模的借位(比如将指向值的指针传递给FFI)。
答案 2 :(得分:1)
我将f4()
的正文放在main()
中并为Drop
实施Bar2
以找出它何时被删除(即超出范围):
impl<'b> Drop for Bar2<'b> {
fn drop(&mut self) { println!("dropping Bar2!"); }
}
结果是:
error: `bar2` does not live long enough
--> <anon>:24:5
|
24 | bar2.f();
| ^^^^ does not live long enough
25 | }
| - borrowed value dropped before borrower
|
= note: values in a scope are dropped in the opposite order they are created
有些东西可疑;让我们用辅助范围详细检查它:
fn main() {
{
let foo = Foo {}; // foo scope begins
{
let mut bar2 = Bar2 { x: &foo }; // bar2 scope begins; bar2 borrows foo
bar2.f();
} // bar2 should be dropped here, but it has the same lifetime as foo, which is still live
} // foo is dropped (its scope ends)
}
我认为此处存在泄漏,bar2
永不丢弃(因此Drop
无法实现)。这就是为什么你不能重新借用它。
答案 3 :(得分:1)
我想补充一下子类型化/差异化在这里扮演的角色。
&mut T
在T
上是不变的。给定两种类型T
和U
,其中T
<U
(T
是U
的子类型),然后&mut T
与&mut U
没有子类型关系(即它们彼此不变),而&T
是&U
的子类型(&T
<&U
)。但是&'lifetime
和&'lifetime mut
相对于'lifetime
是协变的。因此,给定类型'a
的两个生存期'b
和T
,其中'a
的生存期'b
,然后根据子类型关系&'a T
<{{ 1}},类似&'b T
<&'a mut T
提到这个问题,在对函数&'b mut T
的调用中,f
是对self
的引用。编译器将查看其是否可以“暂时缩短” Bar2<'a>
的寿命以适合函数bar2
的调用范围f
,就像'x
和bar2
刚好在调用foo
之前创建,并在f
之后立即消失(即临时缩短:假定在f
内创建变量bar2
并因此在{{1 }}到'x
,其中Bar2<'a>
是原始(实际)生存期)。但是在这里,“缩短”是不可能的。一种是由于对Bar2<'x>
的可变引用,另一种是由于在函数'a
'中对self
和Foo
(Bar2
)的引用具有相同的生存期的定义。首先,由于它是可变引用,因此无法将self
转换为f
,因为Bar2<'a>
和Bar2<'x>
彼此不变。 (请记住,即使&mut Bar2<'a>
或&mut Bar2<'x>
,T < U
对于T > U
也是不变的)。因此,编译器必须使用&mut T
,其次,由于函数&mut U
具有相同的生存期来引用Bar2<'a>
和f
,因此无法转换{{1 }}到Bar2
。因此,这意味着在调用函数Foo
时引用不会被“缩短”,它们将一直有效到块末。
如果忽略了&'a Bar2<'a>
的生存期,则编译器将为&'x Bar2<'a>
提供新的生存期(与f
脱节),这意味着可以“暂时缩短” self
的寿命,然后将其self
引用传递给'b
。即它将Bar2
到mut
,然后将其传递到f
。 (请记住,&'a mut Bar2<'a>
与&'x mut Bar2<'a>
是协变的),因此它将起作用。