Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this borrow still "active"?

Tags:

rust

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?

like image 292
Tom Frank Avatar asked Apr 18 '21 09:04

Tom Frank


2 Answers

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.

like image 79
Deadbeef Avatar answered Sep 28 '22 02:09

Deadbeef


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

Summary

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.

like image 32
Enselic Avatar answered Sep 28 '22 03:09

Enselic