This is a simplified example of something I've encountered:
trait Bla<'a> {
fn create(a: &'a Foo) -> Self;
fn consume(self) -> u8;
}
struct Foo;
impl Foo {
fn take(&mut self, u: u8) {}
}
struct Bar {
foo: Foo,
}
impl Bar {
fn foobar<'a, 'b, B: Bla<'b>>(&'a mut self)
where 'a: 'b {
let u = {
// immutable borrow occurs here
// type annotation requires that `self.foo` is borrowed for `'b`
let foo: &'b Foo = &self.foo;
let b = B::create(foo);
b.consume()
};
// error[E0502]: cannot borrow `self.foo` as mutable because it is also borrowed as immutable
self.foo.take(u);
}
}
Why is self.foo
still considered to be borrowed even after exiting the block it was borrowed on, with everything that used the borrow also dropped?
The data might flow to another place.
To understand why the compiler is correct that the immutable borrow might still exist, consider the following implementation of Bla
that is valid:
#![feature(once_cell)]
// The data stored in this global static variable exists for the entirety of runtime.
static REFS: SyncLazy<Mutex<Vec<&'static Foo>>> = SyncLazy::new(|| Mutex::new(vec![]));
struct Bad;
impl Bla<'static> for Bad {
fn create(a: &'static Foo) -> Self {
// The reference can go somewhere other than `Self`!
REFS.lock().unwrap().push(a);
Bad
}
fn consume(self) -> u8 {
// Even if `self` is consumed here,
// `self` doesn't necessarily destory the immutable borrow.
0
}
}
When your generic foobar
function accepts any valid implementation of Bla
, a valid implementation might take a &'static
borrow that guarantees the borrow to last for the entire runtime. Therefore, it is an error because consume
does not guarantee that the immutable borrow goes away.
You are probably looking for the for<>
syntax as that will allow the Bad
implementation to exist but will disallow anyone from calling foobar
with Bad
:
impl Bar {
fn foobar<B: for<'b> Bla<'b>>(&mut self) {
let u = {
let foo: &Foo = &self.foo;
let b = B::create(foo);
b.consume()
};
self.foo.take(u);
}
}
fn main() {
Bar::foobar::<Bad>(&mut Bar { foo: Foo })
// ^ error: implementation of `Bla` is not general enough
// | `Bad` must implement `Bla<'0>`, for any lifetime `'0`...
// | ...but it actually implements `Bla<'static>`
}
If this isn't the case, you might have to provide more implementation details rather than an example causing the diagnostic error.
Normally, when you don't specify your own lifetime, the lifetime of an immutable borrow to a variable ends when the variable goes out of scope. But in your code, you are specifying an explicit lifetime 'b
of the immutable borrow.
To the borrow checker, the only thing known about the lifetime 'b
is that it is a subset of 'a
(due to where 'a: 'b
). In particular, the borrow checker will not assume that the scope of the immutable borrow ends at }
, because nothing of that sort is specified about 'b
.
The compiler actually says this out loud:
// type annotation requires that
self.foo
is borrowed for'b
Why is self.foo still considered to be borrowed even after exiting the block it was borrowed on, with everything that used the borrow also dropped?
Because the lifetime you specify does not end when the block ends. The borrow checker does not care about that some variables related to the borrow goes out of scope.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With