Need help getting borrow checker to pass with Rc<RefCell<_>>

This is part of a much more complicated scenario, but I've reduced this down to what is closer to a minimal case.

#![allow(unused)]
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

fn main() {
    println!("This won't compile");
}

pub type SmartSheep = Rc<RefCell<Sheep>>;

pub struct Sheep {
    pub name: String,
    pub age: usize,
    pub stats: HashMap<String, String>,
    pub father: Option<SmartSheep>,
}

impl Sheep {
    pub fn new(name: &str, age: usize, father: Option<SmartSheep>) -> SmartSheep {
        let sheep = Sheep {
            name: name.to_string(),
            stats: HashMap::new(),
            age,
            father,
        };
        Rc::new(RefCell::new(sheep))
    }

    pub fn find_with_stat(&mut self, k: &str) -> Option<&String> {
        if self.stats.contains_key(k) {
            return self.stats.get(k);
        }

        match &self.father {
            &None => return None,
            &Some(ref smart_sheep) => {
                let cloned = Rc::clone(smart_sheep);
                let borrowed = cloned.borrow_mut();
                return borrowed.find_with_stat(k);
            }
        }
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (file:///playground)
error[E0597]: `cloned` does not live long enough
  --> src/main.rs:39:32
   |
39 |                 let borrowed = cloned.borrow_mut();
   |                                ^^^^^^ borrowed value does not live long enough
40 |                 return borrowed.find_with_stat(k);
41 |             }
   |             - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 30:5...
  --> src/main.rs:30:5
   |
30 | /     pub fn find_with_stat(&mut self, k: &str) -> Option<&String> {
31 | |         if self.stats.contains_key(k) {
32 | |             return self.stats.get(k);
33 | |         }
...  |
42 | |         }
43 | |     }
   | |_____^

error[E0597]: `borrowed` does not live long enough
  --> src/main.rs:40:24
   |
40 |                 return borrowed.find_with_stat(k);
   |                        ^^^^^^^^ borrowed value does not live long enough
41 |             }
   |             - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 30:5...
  --> src/main.rs:30:5
   |
30 | /     pub fn find_with_stat(&mut self, k: &str) -> Option<&String> {
31 | |         if self.stats.contains_key(k) {
32 | |             return self.stats.get(k);
33 | |         }
...  |
42 | |         }
43 | |     }
   | |_____^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0597`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

This boils down to trying to return a reference that outlives the RefMut itself. This makes sense if you think about it: you have a RefCell that's doing runtime borrow checking. If you were allowed to return a Option<&String> that outlives the RefMut associated with the temporary (runtime) borrow, then the purpose of the RefCell would be defeated as the reference now "escapes" out into the wild.

In general, you want to avoid trying to return references to the caller when you have a RefCell internally. This either doesn't work at all (as is the case here), or you're forced to expose the Ref/RefMut (possibly hiding it behind an impl Deref<...>).

Instead, try to invert the dataflow a bit - have your caller give you an F: FnMut(&String), and then call that closure while holding a scoped borrow to the String inside the method.

3 Likes

Or return the Rc type.

4 Likes

Thanks for the advice. For my use case, it seems like the thing to do is to avoid returning a reference at all, which I can do by cloning the value fetched from the HashMap. The following code works:

#![allow(unused)]
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

fn main() {
    println!("This won't compile");
}

pub type SmartSheep = Rc<RefCell<Sheep>>;

pub struct Sheep {
    pub name: String,
    pub age: usize,
    pub stats: HashMap<String, String>,
    pub father: Option<SmartSheep>,
}

impl Sheep {
    pub fn new(name: &str, age: usize, father: Option<SmartSheep>) -> SmartSheep {
        let sheep = Sheep {
            name: name.to_string(),
            stats: HashMap::new(),
            age,
            father,
        };
        Rc::new(RefCell::new(sheep))
    }

    pub fn find_with_stat(&mut self, k: &str) -> Option<String> {
        if self.stats.contains_key(k) {
            if let Some(s) = self.stats.get(k) {
                return Some(s.clone());
            }
        }

        match &self.father {
            &None => return None,
            &Some(ref smart_sheep) => {
                let mut borrowed = smart_sheep.borrow_mut();
                return borrowed.find_with_stat(k);
            }
        }
    }
}
1 Like