Rc and RefCell clarification

// code snippet
    let v2: RefCell<Vec<RefCell<String>>> = RefCell::new(vec![
        RefCell::new("c".to_string()),
        RefCell::new("d".to_string()),
    ]);

    // note Rc is the difference from v2 above.
    let mut v3: Rc<RefCell<Vec<RefCell<String>>>> = Rc::new(RefCell::new(vec![
        RefCell::new("c".to_string()),
        RefCell::new("d".to_string()),
    ]));

    // works
    *v2.borrow_mut()[1].borrow_mut() = "Z".to_string();
    v2.borrow_mut().push(RefCell::new("W".to_string()));

    println!("v3[1]:{}", v3.borrow()[1].borrow());
    println!("v3[1]:{}", v3.borrow()[1].borrow_mut());
    *v3.borrow()[1].borrow_mut() = "ZZ".to_string(); // individual vector element mutation works too 
    println!("v3[1]:{}", v3.borrow()[1].borrow_mut());

    // this like does not compile:
    // +++++ ******
    v3.borrow_mut().push(RefCell::new("Y".to_string())); // compile error
    // ----- ******
    // compiler error:
    // no method named `push` found for mutable reference `&mut std::rc::Rc<std::cell::RefCell<std::vec::Vec<std::cell::RefCell<std::string::String>>>>` in the current scope
    // method not found in `&mut Rc<RefCell<Vec<RefCell<String>>>>`

Hello
I am just trying to get my Rc and RefCell understanding.

In above code the variables v2 and v3 differ in v3 being Rc.
I believe Rc<RefCell.... still wont allow me to mutate the Vec (push/pop methods).

I am able to mutable individual vector elements in v2 and v3 and push() new elements for v2. But since v3 is protected by Rc and despite of RefCell I am still not able to push() or pop() the vector even though I am still allowed to modify individual elements of vector.

I am trying to understand if there is a syntax to allow me to use push()/pop() for v3 given I have it wrapped in Rc.

thank you.

Are you importing the Borrow and/or BorrowMut traits? Those can interefere with Rc<RefCell<_>> because they are implemented for all types. Try removing the imports.

2 Likes

YOU NAILED IT !!

this is a major trap for new players!

I removed (use std::borrow::BorrowMut;) it and now my code works.
THANK YOU. I thought I was going nuts....

Question: with these use:: imports - for Rc, Arc, RefCell, Cell, etc - what are the use imports best practices so I dont step on a landmine in future?

I clearly got it very wrong by importing everything I thought I would need, just in case.

It seems that "less is more" ! LOL

Types can be imported without any implicit problems (the only problem is the direct name clash, and it is explicitly called out when it arises). Problems can arise when you import traits.

1 Like

This particular situation is a design misstep in std. They shouldn't have used the same names for RefCell's borrowing and the Borrow/BorrowMut traits methods. Especially because the traits are implemented for everything -- that means that if you happen to call .borrow or .borrow_mut on anything that doesn't have inherent methods with that name, the compiler will suggest you import them. But as this thread shows, if you do that and are also using RefCell, sadness will probably result.

Additionally the only time you typically need those traits is if you need to repeat the bounds on keyed collections like HashMap and BTreeMap (or if you're implementing them yourself). So my advice is: avoid importing them when possible; for example if you need them in bounds only, just write out some path to the trait instead of importing the trait.

where Q: std::borrow::Borrow<K>,

But particularly if you're also using RefCell.

Diagnostics and documentation could definitely be improved here.


The reason the code works without the imports is that method resolution will eventually find the RefCell inherent method via the auto-derefing resolution algorithm. And the reason the code doesn't work with the imports is that it finds the trait implementation for Rc<_> before it gets around to the auto-deref portion of the algorithm that considers RefCell (since like everything else, Rc<_> implements the trait).

And the main way to spot the problem is just experience: you called borrow_mut but didn't get a RefMut in the error; instead the compiler was still looking for push at the outer Rc level.

2 Likes

It's simple. Don't import anything you don't need.

all great advice, thanks again.

Is this particular case something that either Clippy or Rust compiler could emit a warning or a friendly-tip about?

I agree the diagnostics could be improved here. There are a few issues about it already...

You could add your experiences, but it probably just needs a dev to take up the cause.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.