Sharing a RefCell to a method

Hi,

New to Rust, I am having some issues when using RefCell. I need to retrieve some data from an API. To avoid making too many requests, I have a structure with an hashmap serving as a cache.

I was thinking RefCell would be the solution to my problem but the data doesn’t seem to live after my populate method. I have made a simplified version below to illustrate.

use std::{cell::RefCell, collections::HashMap};

struct Database {
    my_map: RefCell<HashMap<String, usize>>,
}

impl Database {
    fn search(&self, key: &str) {
        if self.my_map.borrow().is_empty() {
            populate(RefCell::clone(&self.my_map));
        }
        if let Some(value) = self.my_map.borrow().get(key) {
            println!("Key: {}, Value {}", key, value);
        } else {
            println!("{} not found", key);
        }
    }
}

fn populate(map: RefCell<HashMap<String, usize>>) {
    println!("Populating my_map");
    map.borrow_mut().insert("a".to_string(), 1);
    map.borrow_mut().insert("b".to_string(), 2);
    map.borrow_mut().insert("c".to_string(), 3);
    println!("The map has {} elements after populate", map.borrow().len());
}

fn main() {
    let db = Database {
        my_map: RefCell::new(HashMap::new()),
    };
    db.search("a");
    db.search("b");
    println!("The end: the map has {} elements", db.my_map.borrow().len());
}
$ cargo run
Populating my_map
The map has 3 elements after populate
a not found
Populating my_map
The map has 3 elements after populate
b not found
The end: the map has 0 elements

Rust playground

What am I missing?

Thank you

This creates a completely new RefCell that has nothing to do with self.my_map, the clone being completely separate from the original value. You probably wanted to wrap the RefCell in an Rc (a reference counted smart pointer type that allows shared ownership) and clone that, creating two pointers to the same RefCell. That'd be the classic single threaded interior mutablilty setup in Rust.

3 Likes

I see, I thought cloning the refcell would still point to the same data but that’s more the Rc.

Indeed it seems to work with

use std::{cell::RefCell, collections::HashMap, rc::Rc};

struct Database {
    my_map: Rc<RefCell<HashMap<String, usize>>>,
}

impl Database {
    fn search(&self, key: &str) {
        if self.my_map.borrow().is_empty() {
            populate(self.my_map.clone());
        }
        …
    }
}

fn populate(map: Rc<RefCell<HashMap<String, usize>>>) {
    …
}

fn main() {
    let db = Database {
        my_map: Rc::new(RefCell::new(HashMap::new())),
    };
    …
}

Thanks !

1 Like

In this case, Rc is actually overkill; a simpler solution is:

use std::{cell::RefCell, collections::HashMap};

struct Database {
    my_map: RefCell<HashMap<String, usize>>,
}

impl Database {
    fn search(&self, key: &str) {
        let mut my_map = self.my_map.borrow_mut();
        if my_map.is_empty() {
            populate(&mut my_map);
        }
        if let Some(value) = my_map.get(key) {
            println!("Key: {}, Value {}", key, value);
        } else {
            println!("{} not found", key);
        }
    }
}

fn populate(map: &mut HashMap<String, usize>) {
    println!("Populating my_map");
    map.insert("a".to_string(), 1);
    map.insert("b".to_string(), 2);
    map.insert("c".to_string(), 3);
    println!("The map has {} elements after populate", map.len());
}

fn main() {
    let db = Database {
        my_map: RefCell::new(HashMap::new()),
    };
    db.search("a");
    db.search("b");
    println!("The end: the map has {} elements", db.my_map.borrow().len());
}
2 Likes

You seem to be populating the cell only if it’s empty. Such “initialize (lazily) once” operations can also use OnceCell:

use std::{cell::OnceCell, collections::HashMap};

struct Database {
    my_map: OnceCell<HashMap<String, usize>>,
}

impl Database {
    fn search(&self, key: &str) {
        let my_map = self.my_map.get_or_init(|| populate());
        if let Some(value) = my_map.get(key) {
            println!("Key: {}, Value {}", key, value);
        } else {
            println!("{} not found", key);
        }
    }
}

fn populate() -> HashMap<String, usize> {
    let mut map = HashMap::new();
    println!("Populating my_map");
    map.insert("a".to_string(), 1);
    map.insert("b".to_string(), 2);
    map.insert("c".to_string(), 3);
    println!("The map has {} elements after populate", map.len());
    map
}

fn main() {
    let db = Database {
        my_map: OnceCell::new(),
    };
    db.search("a");
    db.search("b");
    println!(
        "The end: the map has {} elements",
        db.my_map.get().unwrap().len()
    );
}
3 Likes

Thank you both (and for the discovery of OneCell), I can indeed simplify my code :slight_smile:

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.