3

I am trying to return a moved value from an Rc:

if let Some(last_elem) = self.tail.take() {
    let last = Rc::clone(&last_elem);
    let tmp_node = last.borrow();
    let tmp = tmp_node.deref();
    return Some(*tmp);
}

Where:

  • self.tail has type Option<Rc<RefCell<Node<T>>>>;
  • after borrow the tmp_node has type Ref<Node<T>>; and
  • I would like to return Option<Node<T>>.

However the compiler complains, "cannot move out of *tmp which is behind a shared reference".

How can I fix this?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
En Xie
  • 510
  • 4
  • 19
  • 6
    *"cannot move out of *tmp which is behind a shared reference"* should be clear. You don't own the value, you can't return it. You may clone it if you want. – Denys Séguret Dec 18 '21 at 15:22
  • Unlike in Python or Java, Rust values aren't implicitly heap-allocated. In order to return a value stored somewhere, you need to either move it from that location (and leave something else there), or clone it. – user4815162342 Dec 18 '21 at 15:34
  • @DenysSéguret I try clone change the like return Some(*(tmp.clone())); it is still complain about "cannot move out of a shared reference" – En Xie Dec 18 '21 at 16:18
  • 1
    @EnXie, could you provide a [mre]? I have a possible answer to the problem you're having with `clone()`, but can't test it myself since the code given isn't sufficient to reproduce the situation. – Charles Duffy Dec 18 '21 at 17:07
  • @CharlesDuffy thank you very much, I give up on move data from Rc. Instead I create a new Obj for return. – En Xie Dec 19 '21 at 01:49

1 Answers1

8

In general, it's impossible to move a value out of Rc, since it might be read concurrently from somewhere else.

However, if your code's logic can guarantee that this Rc is the sole owner of the underlying data, there's an escape hatch - Rc::try_unwrap, which performs the check at runtime and fails if the condition is not fulfilled. After that, we can easily unwrap the RefCell (not Ref!) with RefCell::into_inner:

pub fn unwrap<T>(last_elem: Rc<RefCell<T>>) -> T {
    let inner: RefCell<T> = Rc::try_unwrap(last_elem)
        .unwrap_or_else(|_| panic!("The last_elem was shared, failed to unwrap"));
    inner.into_inner()
}

Playground


Another possible approach, if you want not to move value from Rc but to get a copy, would be to go with your original approach, but use clone instead of deref:

pub fn clone_out<T: Clone>(last_elem: Rc<RefCell<T>>) -> T {
    last_elem.borrow().clone()
}

A side note: looks like you're trying to implement some kind of linked list. This is a notoriously hard problem to do in Rust, since it plays very bad with the single-ownership semantics. But if you're really sure you want to go though all the dirty details, this book is highly recommended.

Cerberus
  • 8,879
  • 1
  • 25
  • 40
  • is that possible to clone it and move the variable as return ? – En Xie Dec 18 '21 at 16:22
  • Of course, see the edit. – Cerberus Dec 18 '21 at 20:08
  • Why `unwrap_or_else(|| panic!())` instead of just `expect()`? – Chayim Friedman Aug 03 '23 at 11:29
  • Good catch, thank you! Can't remember why I decided this way, so edited. – Cerberus Aug 03 '23 at 16:37
  • Enlightening. Your link introduces me to Playground for the first time. But this doesn't seem to build ("`T` doesn't implement `Debug`")... is this intended? Also it would no doubt be more useful to have an example which *does* something (trivial), particularly in a `RefCell` context, where things can mess up at runtime. – mike rodent Aug 03 '23 at 19:53
  • ...aha, thank you, missed that - that seems to be the reason for not using `expect` at the first place (so rolled back the previously mentioned change). As for the "example which does something" - do you mean runnable example, with `main`? – Cerberus Aug 04 '23 at 15:16