I'm fighting with the borrow checker. I have two similar pieces of code, one working as I expect, and the other not.
The one that works as I expect:
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)
}
}
The one that doesn't:
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]
}
}
I hoped I could call Bar2::f
twice without irritating the compiler, as in case 1.
The question is in the comment (3): who borrowed bar2
, whereas there is no affectation?
Here's what I understand:
In case 1,
f2
call: the lifetime parameter'a
is the one of the receiving&Foo
value, so this lifetime is empty when there is no affectation, andbar
is not borrowed after theBar1::f
call;In case 2,
bar2
borrowsfoo
(as immutable), so the lifetime parameter'b
inBar2
struct is thefoo
reference lifetime, which ends at the end off4
body. CallingBar2::f
borrowsbar2
for that lifetime, namely to the end off4
.
But the question is still: who borrowed bar2
? Could it be Bar2::f
? How Bar2::f
would hold the borrowed ownership after the call? What am I missing here?
I'm using Rust 1.14.0-nightly (86affcdf6 2016-09-28) on x86_64-pc-windows-msvc.
I put the body of
f4()
in amain()
and implementedDrop
forBar2
to find out when it is dropped (i.e. goes out of scope):And the result was:
Something's fishy; let's examine it in detail, with helper scopes:
It looks to me that there is a leak here and
bar2
is never dropped (and thusDrop
cannot be implemented for it). That's why you cannot re-borrow it.Ah... you basically self-borrowed yourself.
The issue hinges on the fact that you have the same lifetime (
'b
) used for both the lifetime ofFoo
and the lifetime ofBar
. The compiler then dutifully unifies those lifetimes, and you end up in a strange situation where suddenly the lifetime of the borrow which should have ended at the end of the statement instead ends after the value should have gone out of scope.As a rule of thumb: always use a fresh lifetime for
self
. Anything else is weird.It's interesting to note that this pattern can actually be useful (though more likely with an immutable borrow): it allows anchoring a value to a stack frame, preventing any move after the call to the function, which is (sometimes) useful to represent a borrow that is not well-modeled by Rust (like passing a pointer to the value to FFI).
In case #2, you have this:
To highlight:
&'b mut self
and&'b Foo
have the same lifetime specified.This is saying that the reference to
self
and the returned reference to an instance of aFoo
both have the same lifetime. Looking at the call site, you have this:So the compiler is inferring that both
foo
andbar2
have the same lifetime. The lifetime offoo
is the scope of thef4
function, and so the mutable reference tobar2
shares this.One way to fix this, is to remove the explicit lifetime on the
self
reference:This compiles and the compiler correctly understands that the reference to
bar2
and the reference tofoo
have different lifetimes.Playground: https://play.rust-lang.org/?gist=caf262dd628cf14cc2884a3af842276a&version=stable&backtrace=0
TLDR: Yes, having the same lifetime specifier on the self reference and the returned reference means that the entire scope of
f4
holds a mutable borrow ofbar2
.I would like to add about the roles that subtyping/variance play here.
&mut T
is invariant overT
. Given two typesT
andU
, whereT
<U
(T
is a subtype ofU
), then&mut T
has no subtyping relation with&mut U
(i.e they are invariant with each other), whereas&T
is a subtype of&U
(&T
<&U
). But&'lifetime
and&'lifetime mut
, both are covariant over'lifetime
. So given two lifetimes'a
and'b
for a typeT
, where'a
outlives'b
, then as per subtyping relation&'a T
<&'b T
, similarly&'a mut T
<&'b mut T
Coming to the question, in the call to function
f
,self
is a reference toBar2<'a>
. The compiler will see if it can "temporarily shorten" the life ofbar2
to fit around the scope of the functionf
's invocation say'x
, as ifbar2
andfoo
were created just beforef
is called and go away immediately afterf
(i.e temporary shortening: assuming variablebar2
created within'x
and henceBar2<'a>
toBar2<'x>
,'a
being the original (real) lifetime). But here, "shortening" is not possible; One, because of mutable reference toself
and two, same lifetime on references toFoo
as well asBar2
(self
), in the functionf
's definition. Firstly, since it is a mutable reference, it can't convertBar2<'a>
toBar2<'x>
, because&mut Bar2<'a>
and&mut Bar2<'x>
are invariant with each other. (remember even ifT < U
orT > U
, then&mut T
is invariant with&mut U
). So the compiler has to go withBar2<'a>
and secondly, since the functionf
is having the same lifetimes for references toBar2
andFoo
, can't convert&'a Bar2<'a>
to&'x Bar2<'a>
. So it means the references aren't "shortened" when calling the functionf
and they will remain valid till the end of the block.If
self
's lifetime is elided, then the compiler will give a fresh lifetime to theself
(disjoint with'b
), which means it is free to "temporarily shorten" the life ofBar2
and then pass it'smut
reference tof
. i.e It will do&'a mut Bar2<'a>
to&'x mut Bar2<'a>
and then pass it tof
. (remember&'lifetime mut
is covariant over'lifetime
) and hence it will work.