Take and assign to a RefCell

I would like to do something like this, specifically for a non-Copy type:

playground

use std::cell::RefCell;

fn main() {
    map_ref(RefCell::new("hello".to_string()), |x| format!("{} world", x));
}

fn map_ref(foo: RefCell<String>, f: impl FnOnce(String) -> String) {
    let mut lock = foo.borrow_mut();
    *lock = f(*lock);
}

This doesn't work, since it can't deref out of a RefMut. Is the only safe way around this to use a level of indirection, e.g. an Option like this?

playground

use std::cell::RefCell;

fn main() {
    map_ref(RefCell::new(Some("hello".to_string())), |x| format!("{} world", x));
}

fn map_ref(foo: RefCell<Option<String>>, f: impl FnOnce(String) -> String) {
    let mut lock = foo.borrow_mut();
    *lock = Some(f(lock.take().unwrap()));
}

As far as the example goes, you can temporarily replace the String with the empty string (the result of <String as Default>::default()) by utilizing std::mem::take:

fn map_ref(foo: RefCell<String>, f: impl FnOnce(String) -> String) {
    let mut lock = foo.borrow_mut();
    *lock = f(std::mem::take(&mut *lock));
}

Playground.

More generally, see the blog post: Moving from borrowed data and the sentinel pattern

3 Likes

The problem is that something can panic and the reference with be left uninitialized. There are crates like https://crates.io/crates/replace_with, however.

3 Likes

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.