Search code examples
rustsmart-pointersdereference

Unwrap and access T from an Option<Rc<RefCell<T>>>


I am trying to solve some Leetcode problems with Rust. However, I ran into some difficulties with LeetCode's TreeNode implementation.

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

// TreeNode data structure
#[derive(Debug, PartialEq, Eq)]
pub struct TreeNode {
    pub val: i32,
    pub left: Option<Rc<RefCell<TreeNode>>>,
    pub right: Option<Rc<RefCell<TreeNode>>>,
}

impl TreeNode {
    #[inline]
    pub fn new(val: i32) -> Self {
        TreeNode {
            val,
            left: None,
            right: None,
        }
    }
}

If I want to do an inorder traversal, how to unwrap the TreeNode's Option<Rc<RefCell<TreeNode>>> object, access its .val .left .right and pass them as inputs into a recursive function?

I have tried:

pub struct Solution;

impl Solution {
    pub fn inorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        let mut ret: Vec<i32> = vec![];
        match root {
            Some(V) => Solution::helper(&Some(V), &mut ret),
            None => (),
        }

        ret
    }

    fn helper(node: &Option<Rc<RefCell<TreeNode>>>, ret: &mut Vec<i32>) {
        match node {
            None => return,
            Some(V) => {
                // go to the left branch
                Solution::helper(
                    (*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
                        .into_inner()
                        .left,
                    ret,
                );
                // push root value on the vector
                ret.push(Rc::try_unwrap(Rc::clone(V)).unwrap_err().into_inner().val);
                // go right branch
                Solution::helper(
                    (*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
                        .into_inner()
                        .right,
                    ret,
                );
            }
        }
    }
}

fn main() {}

(Playground)

The compiler complains:

error[E0308]: mismatched types
  --> src/lib.rs:42:21
   |
42 | /                     (*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
43 | |                         .into_inner()
44 | |                         .left,
   | |_____________________________^ expected reference, found enum `std::option::Option`
   |
   = note: expected type `&std::option::Option<std::rc::Rc<std::cell::RefCell<TreeNode>>>`
              found type `std::option::Option<std::rc::Rc<std::cell::RefCell<TreeNode>>>`
help: consider borrowing here
   |
42 |                     &(*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
43 |                         .into_inner()
44 |                         .left,
   |

But if I try the suggestion, it complains as well:

error[E0507]: cannot move out of an `Rc`
  --> src/lib.rs:42:22
   |
42 |                     &(*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of an `Rc`

error[E0507]: cannot move out of data in a `&` reference
  --> src/lib.rs:42:22
   |
42 |                     &(*Rc::try_unwrap(Rc::clone(V)).unwrap_err())
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                      |
   |                      cannot move out of data in a `&` reference
   |                      cannot move

Solution

  • Unwrap and access T from an Option<Rc<RefCell<T>>>

    You really don't want to try to remove the value from the Option, the Rc or the RefCell via unwrap / try_unwrap / into_inner. Instead, pattern match on the Option and then call borrow on the RefCell to get a reference to the T.

    Additionally:

    1. Use if let instead of a match statement when you only care about one arm.
    2. Variables use snake_case. V is not an appropriate name.
    3. There's no need to use a struct here, or to define the helper function publicly. A plain function and a nested function are simpler and exposes less detail.
    4. There's no need to provide an explicit type when constructing ret.
    pub fn inorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        fn helper(node: &Option<Rc<RefCell<TreeNode>>>, ret: &mut Vec<i32>) {
            if let Some(v) = node {
                let v = v.borrow();
    
                helper(&v.left, ret);
                ret.push(v.val);
                helper(&v.right, ret);
            }
        }
    
        let mut ret = vec![];
    
        if let Some(v) = root {
            helper(&Some(v), &mut ret);
        }
    
        ret
    }
    

    Personally, I'm not a fan of being forced to construct the Some, so I'd probably reorganize the code, which also allows me to stick it as a method on TreeNode:

    impl TreeNode {
        pub fn inorder_traversal(&self) -> Vec<i32> {
            fn helper(node: &TreeNode, ret: &mut Vec<i32>) {
                if let Some(ref left) = node.left {
                    helper(&left.borrow(), ret);
                }
    
                ret.push(node.val);
    
                if let Some(ref right) = node.right {
                    helper(&right.borrow(), ret);
                }
            }
    
            let mut ret = vec![];
            helper(self, &mut ret);
            ret
        }
    }
    

    See also: