3

First of all, I apologize if this question has been asked before. The only similar problem I could find was this (but it seems different) :

Cyclic reference does not live long enough

My code is :

use std::cell::RefCell;
use std::rc::Rc;

type NodePointer = Option<Rc<RefCell<Node>>>;

#[derive(Debug)]
struct Node {
    pub value : i32,
    pub next : NodePointer
}

fn main() {
    let mut vector = vec![None; 2];
    let new_node = Rc::new(RefCell::new(Node {
                value : 0,
                next : None
            }));
    vector[1] = Some(Rc::clone(&new_node));
    let new_node = Rc::new(RefCell::new(Node {
                value : 0,
                next : Some(Rc::clone(&new_node))
            }));
    vector[0] = Some(new_node);
    println!("{:?}", vector);

    // the following 3 lines would represent a body of the loop
    let mut a = vector[0].as_ref(); // Option<&Rc<RefCell<Node>>>
    let b = a.take().unwrap().borrow_mut(); // RefMut<Node>
    a = b.next.as_ref(); //ERROR : borrowed value 'b' does not live long enough

    println!("{:?}", vector);
}

The code below presents a short excerpt of my complete code. It is a bit weird, but variable 'a' would be used for looping over a vector (more than 2 values in complete code).

What I am trying to do is to make sure that variable 'a' is replaced with 'next' argument from vector[0] without modifying vector.

Compiler complains that 'b' does not live long enough, but I don't see why this is the case.

According to my understanding :

  • vector[0] is Option<Rc<...>>
  • variable 'a' is Option<&Rc<...>>
  • a.take() does not modify vector[0]
  • a.take() instead replaces 'a' with None
  • borrow_mut() should give a reference to vector[0], but for some reason it doesn't (?) <- I think this is the problem here
  • something happened to b, but I don't see what.

I am also aware that I could make use of Option::take() method instead of take_ref() (and it works in my complete code together with some additional modifications), but I would like to keep vector unmodified in-between 2 println statements

EDIT : for information, the following loop body would compile, but it modifies vector...

let mut a = vector[0].take(); // Option<&Rc<RefCell<Node>>>
let temp = a.unwrap();
let mut b = temp.borrow_mut(); // RefMut<Node>
a = b.next.take(); //ERROR : borrowed value 'b' does not live long enough
  • 1
    `borrow_mut() should give a reference to vector[0], but for some reason it doesn't` -- actually it's expected to return the guard object that allows you to mutably borrow the contents of `RefCell` without a mutable borrow on the `RefCell` itself. – Marko Topolnik May 15 '22 at 19:10

2 Answers2

2

I may be wrong (still not 100% confident about the borrow checker), but I think the issue is this:

    // b borrows from a
    let b = a.take().unwrap().borrow_mut();
    // now, a borrows from b
    // note that the lifetime of `a` doesn't magically change just because you're not using the original value anymore
    // so from the type checker's perspective, a and b borrow from each other at the same time – not allowed
    a = b.next.as_ref();
    
    // btw, if you replace `a =` with `let c =` or even `let a =`, creating a new variable with a new lifetime, it compiles
    // let a = b.next.as_ref();
    // but doesn't work for us since you want to put it in a loop

And I think it makes sense. In the first iteration, you get a Option<&Rc<_>> by borrowing from vector. But if you had a Option<&Rc<_>> in the second iteration, where would it be borrowed from? You didn't take it from vector, you took it from stuff that only lived during the last iteration – it might be invalid. Or otherwise you would somehow need to ensure that all of the intermediate RefMuts somehow lived for the duration of the loop.

I don't think you can borrow something from one iteration to bring it to the next.

Instead, you should take advantage of the reference counting that you already have:

    let mut next = vector[0].clone();
    while let Some(node) = next {
        next = node.borrow_mut().next.clone();
    }

Now, next has type Option<Rc<_>> – shared ownership instead of a borrow.

Reinis Mazeiks
  • 1,168
  • 1
  • 10
  • 21
  • I found out the same as you with `let c`, but my impression was that reassignment of a mutable variable is treated almost the same as the dropping of the previous and the creation of a new variable. – Marko Topolnik May 15 '22 at 19:27
  • Aaah,I wasn't aware. I recommend you to add the following link in your answer : https://stackoverflow.com/questions/58674830/how-to-clone-an-option-of-rc-in-rust This will complete your answer :) – rostyslav52 May 16 '22 at 18:37
1

I'd just like to expand on the answer given by Reinis Mazeiks a bit. I've modified your code slightly to remove some of the non-critical stuff.

let mut a = vector[0].as_deref();
let b = a.unwrap().borrow();
a = b.next.as_deref();

The yields the same error of "b does not live long enough." The variable a is borrowing from vector, and b is transitively borrowing from vector as well. For instance, the following will compile:

let b: Ref<'_, Node>;
{
    let a = vector[0].as_deref();
    b = a.unwrap().borrow();
} // a is dropped
// This is okay because b's lifetime is tied to vector
println!("{}", b.value);

However, borrow returns an owned value, a Ref<'_, T>. On the third line in the first code block, the compiler doesn't see us borrowing from a, or vector, is sees us borrowing from b. This is because Deref (which is implicitly called when you call as_ref in your code), takes an &self, so we can only borrow something through Deref for at most as long as it lives, and that Ref lives only as long as b.

Now we run into a pickle here. Let's label some lifetimes and unpack things a bit:

let mut a = vector[0].as_deref(); // Option<&'a RefCell<Node>>
let b = a.unwrap().borrow(); // Ref<'b, Node>
let temp = b.next.as_deref(); // Option<&'c RefCell<Node>>
a = temp;

The second line requires that 'a: 'b (read 'a lives at least as long as 'b). This is because that Ref is borrowing from the reference held by a, and that reference must be valid for as long as b lives. When creating temp, we then borrow from b, so the lifetime 'b must satisfy 'b: 'c, but more importantly 'c also cannot outlive the variable b, since it's borrowing through the owned Ref which must be dropped. However the fourth line requires that 'c: 'a in order to follow the rules of variance and sub-typing. Since we're assigning something that lives for 'c to something that lives for 'a, it follows that 'c must be at least as long as 'a. Overall this comes full-circle to say that 'a = 'c. But this is a problem because 'a and 'c come from different values which are dropped at different times, and because Ref does not allows its generic arguments to dangle in its drop implementation, our program gets rejected by the drop check.

For fun, I copied the relevant parts of std's RefCell implementation, but changed the drop implementation of BorrowRef (which Ref contains) to the following:

unsafe impl<#[may_dangle] 'a> Drop for BorrowRef<'a> {
    #[inline]
    fn drop(&mut self) {
        // This is UB
        let borrow = self.borrow.get();
        debug_assert!(is_reading(borrow));
        // This is also UB
        self.borrow.set(borrow - 1);
    }
}

Using the custom RefCell with this modification allows the three problematic lines to compile, validating the theoretical argument above. However, do not even think about doing this because the drop implementation uses references which live for 'a, so this is undefined behavior, and hence why your program was rejected.

Ian S.
  • 1,831
  • 9
  • 17
  • In your first code block, if we just add `let` at the beginning of the 3rd line: `let a = b.next.as_deref();`, then it compiles. I'm having a hard time understanding why this makes a difference, since in both cases it should be the same outcome in terms of lifetimes. It compiles even if we use a completely new variable, `let c = ...`, letting `a` live on. – Marko Topolnik May 16 '22 at 07:30
  • When you change the third line to be `let a`, then you're no longer assigning to the same variable, meaning that final constraint of `'c: 'a` is no longer required. The type of `a` on the third line would just be `Option<&'c RefCell>`, which doesn't cause any cyclic constraints which were the root of the issue – Ian S. May 16 '22 at 13:05
  • Also, upon rereading my answer I found a couple ill-placed typos and decided to clarify some things, so rereading it now will hopefully make more sense. – Ian S. May 16 '22 at 13:41
  • 1
    It seems that the solution `'a == 'b == 'c` is acceptable, just not accepted by the current borrow checker. [Here it is](https://godbolt.org/z/951rd7K95) working with nightly rustc. – Marko Topolnik May 16 '22 at 17:24
  • There seems to be more going on here with the nightly compiler. If we force `a` to have a non-trivial `Drop` implementation then it [fails to compile](https://godbolt.org/z/oPzzjo3M9) with a similar error to the original. If we just make `a` non-`Copy`, then the drop order it decides on is `a`, then `b`, then `vector`. I honestly don't have a great argument for what's going on here, especially when it comes to `a`'s `Drop` impl affecting everything. If it's fine to drop first with an empty drop impl, why does that change with a non-empty impl? I might be worth asking a compiler team member – Ian S. May 16 '22 at 18:37
  • @IanS. Do you know how to attract attention of compiler team? Or should I just hope that they will notice my question eventually? :p Answer given by Reinis is enough for my situation, but I am curious what is going on here. – rostyslav52 May 16 '22 at 18:42