Where exactly does a value exits scope?

Recently when writing a doubly linked list in a LRU data structure, I have encountered the following problem:

struct LRUCache {
    capacity: usize,
    head: Rc<RefCell<Node>>,
    tail: Rc<RefCell<Node>>,
    map: HashMap<i32, Rc<RefCell<Node>>>,
}


...
    if self.map.len() == self.capacity as usize {
                self.map.remove(&self.tail.borrow().prev.as_ref().unwrap().borrow().key);
                self.remove_node(self.tail.borrow().prev.as_ref().unwrap().clone());
    }
...

fn remove_node(&self, node: Rc<RefCell<Node>>) {
        let prev = node.borrow_mut().prev.take().unwrap().clone();
        let next = node.borrow_mut().next.take().unwrap().clone();
        next.borrow_mut().prev = Some(Rc::clone(&prev));
        prev.borrow_mut().next = Some(Rc::clone(&next));
}

the line

self.remove_node(self.tail.borrow().prev.as_ref().unwrap().clone());

panics that the tail node was being borrowed while the following line trying to get a mutable reference to it.

next.borrow_mut().prev = Some(Rc::clone(&prev));

But I was expecting the code above should work just fine since clone has the following function signature according to Lifetime Elision

fn clone<'a>(&'a self) -> Self { ... }

where the lifetime annotation 'a indicates that the reference needs only to live until this function returns, and the reference returned by RefCell::borrow(self.tail) wasn't stored in a variable, which might extend its lifetime. It should have been fine for the compiler to just discard that reference at the end of the clone function, but it didn't. If I put the clone part in another statement as following:

let n = self.tail.borrow().prev.as_ref().unwrap().clone();
self.remove_node(n);

It works properly, which indicates that the borrowed reference was discarded at least before the self.remove_node(n) statement. This makes me wondering when exactly could a variable be dropped in rust. According to the example described above, it seems that values introduced temporarily, or in other words, a value that is not stored in a variable, could be dropped as early as the end of that statement and not in the middle of that statement. Is this true or there are some other rules?

A recreation people can play with.

Don't confuse the liveness scope of values with Rust's '_ lifetimes. Those are two different things. Borrow checking (which checks '_ lifetime constraints, among other things) doesn't change the semantics of the code (e.g. by changing where a value drops), it only allows or disallows code.

What we're actually talking about are temporary scopes. Rust's temporary scopes have a bad rep as being unintuitive, in part because yes, there are a lot of rules.

But AFAIK the shortest temporary drop scope is the end of the statement (not the middle of a statement).

3 Likes

Yeah :sweat_smile:

3 Likes

Thank you so much!

Incidentally, doesn't this do the same thing with less work?

1 Like

Yes, it does. My code is a bit repetitive in that part.