Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cyclic reference of RefCell borrows in traversal

I'm learning Rust and tried coding a doubly-linked list. However, I'm stuck already at a typical iterative traversal implementation. I'm getting the impression that the borrow checker / drop checker is too strict and cannot infer the correct lifetime for the borrow when it crosses the function boundary from RefCell. I need to repeatedly set a variable binding (curr in this case) to the borrow of its current contents:

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

pub struct LinkedList<T> {
    head: Option<Rc<RefCell<LinkedNode<T>>>>,
    // ...
}

struct LinkedNode<T> {
    value: T,
    next: Option<Rc<RefCell<LinkedNode<T>>>>,
    // ...
}

impl<T> LinkedList<T> {
    pub fn insert(&mut self, value: T, idx: usize) -> &mut LinkedList<T> {
        // ... some logic ...

        // This is the traversal that fails to compile.
        let mut curr = self.head.as_ref().unwrap();
        for _ in 1..idx {
            curr = curr.borrow().next.as_ref().unwrap()
        }

        // I want to use curr here.
        // ...
        unimplemented!()
    }
}

The compiler complains:

Without NLL

error[E0597]: borrowed value does not live long enough
  --> src/lib.rs:22:20
   |
22 |             curr = curr.borrow().next.as_ref().unwrap()
   |                    ^^^^^^^^^^^^^ temporary value does not live long enough
23 |         }
   |         - temporary value dropped here while still borrowed
...
28 |     }
   |     - temporary value needs to live until here
   |
   = note: consider using a `let` binding to increase its lifetime

With NLL

error[E0716]: temporary value dropped while borrowed
  --> src/lib.rs:22:20
   |
22 |             curr = curr.borrow().next.as_ref().unwrap()
   |                    ^^^^^^^^^^^^^
   |                    |
   |                    creates a temporary which is freed while still in use
   |                    a temporary with access to the borrow is created here ...
23 |         }
   |         -
   |         |
   |         temporary value is freed at the end of this statement
   |         ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `std::cell::Ref<'_, LinkedNode<T>>`
   |
   = note: consider using a `let` binding to create a longer lived value
   = note: The temporary is part of an expression at the end of a block. Consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped.

I would really appreciate a iterative solution (non-recursive) to this problem.

like image 486
Tsukki Avatar asked Oct 24 '25 17:10

Tsukki


2 Answers

You can clone Rc to avoid lifetime issues:

let mut curr = self.head.as_ref().unwrap().clone();
for _ in 1..idx {
    let t = curr.borrow().next.as_ref().unwrap().clone();
    curr = t;
}
like image 195
aSpex Avatar answered Oct 26 '25 09:10

aSpex


Here's a smaller reproduction that I believe shows the same problem:

use std::cell::RefCell;

fn main() {
    let foo = RefCell::new(Some(42));
    let x = foo.borrow().as_ref().unwrap();
}

As I read it:

  1. foo.borrow() returns a cell::Ref, a type of smart pointer. In this case, the smart pointer acts like an &Option<i32>.
  2. as_ref() creates an Option<&i32> where the inner reference has the same lifetime as the smart pointer.
  3. The Option is discarded, yielding only an &i32, still with a lifetime of the smart pointer.

Notably, the smart pointer Ref only lasts for the statement, but the code attempts to return a reference into the Ref that would outlive the statement.

Generally, the solution would be to do something like this:

let foo_borrow = foo.borrow();
let x = foo_borrow.as_ref().unwrap();

This keeps the smart pointer around longer, allowing the lifetime of the reference to be valid for as long as foo_borrow (representing the borrow itself) exists.

In the case of a loop, there's not much you can do, as you essentially want to borrow every previous node until you get to the next one.

like image 34
Shepmaster Avatar answered Oct 26 '25 07:10

Shepmaster



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!