How make RefCell work in this example?

This is a official example and it works.

use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

I did a little change to this code and got errors. How can make it works again.

use std::rc::{Rc, Weak};
use std::cell::RefCell;
use std::thread;

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    thread::spawn(move || {
        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
    }).join().unwrap();

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

RefCell is not safe to use in a multi-threaded context, so Rust has been smart enough to prevent you from doing so.

You need to replace RefCell by RwLock, and instead of .borrow() you use .read().unwrap() and instead of .borrow_mut() you use .write().unwrap().

If you don't like the .unwrap()s, you can use ::parking_lot::RwLock.

1 Like

Similarly, Rc is not thread safe - you'll need to switch to Arc (which does the same thing, but uses an atomic counter).

2 Likes

Here is what I did, unfortunately I still can't compile it successfully. I'm new to rust-lang.
I spent couple hours on it, I found it's difficult to be happy by coding in rust-lang.
Can any one post code that compiles?

use std::cell::RefCell;
use std::thread;
use std::sync::RwLock;
use std::sync::Arc;
use std::sync::Weak;

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RwLock<Weak<Node>>,
    children: RwLock<Vec<Arc<Node>>>,
}

fn main() {
    let leaf = Arc::new(Node {
        value: 3,
        parent: RwLock::new(Weak::new()),
        children: RwLock::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.read().unwrap().upgrade());

    let branch = Arc::new(Node {
        value: 5,
        parent: RwLock::new(Weak::new()),
        children: RwLock::new(vec![Arc::clone(&leaf)]),
    });

    // let leaf = leaf.clone();
    thread::spawn(move || {
        *leaf.parent.write().unwrap() = Arc::downgrade(&branch);
    }).join().unwrap();

    println!("leaf parent = {:?}", leaf.parent.read().unwrap().upgrade());
}

You're almost there with the leaf.clone() you have commented out -- you just need to assign that to a variable that isn't leaf so you can move it into the closure without consuming the original Arc:

let leaf_clone = Arc::clone(&leaf); // instead of leaf.clone() which is ambiguous
thread::spawn(move || {
    *leaf_clone.parent.write().unwrap() = Arc::downgrade(&branch);
}).join().unwrap();

This will print out None twice, because branch is also being moved in to the closure. When branch is dropped, there are no strong references remaining to keep the Arc<Node> alive, so upgrade() in the final line always returns None.

Presumably in a real program branch would be a clone of an Arc being stored somewhere else, so this wouldn't be a problem -- however just to make the example work, you can clone it and store the clone in an unused variable to keep it alive until the end of main.

let _branch = Arc::clone(&branch);
let leaf_clone = Arc::clone(&leaf);
thread::spawn(move || {
    *leaf_clone.parent.write().unwrap() = Arc::downgrade(&branch);
}).join().unwrap();

Playground.

1 Like

@trentj Thank you very much. Obviously it's not a very efficient solution. It allocate memory twice. I just want to change the value of the pointer with second thread.
I feel like the language is on the verge of success.
Can any one tell me the rust-lang designer's email?
Here is my final code.

use std::cell::RefCell;
use std::thread;
use std::sync::RwLock;
use std::sync::Arc;
use std::sync::Weak;

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RwLock<Weak<Node>>,
    children: RwLock<Vec<Arc<Node>>>,
}

fn main() {
    let leaf = Arc::new(Node {
        value: 3,
        parent: RwLock::new(Weak::new()),
        children: RwLock::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.read().unwrap().upgrade());

    let branch = Arc::new(Node {
        value: 5,
        parent: RwLock::new(Weak::new()),
        children: RwLock::new(vec![Arc::clone(&leaf)]),
    });

    modify_tree(&leaf, &branch);
    println!("leaf parent = {:?}", leaf.parent.read().unwrap().upgrade());
}

fn modify_tree(leaf: &Arc<Node>, branch: &Arc<Node>) {
    let leaf = Arc::clone(&leaf);
    let branch = Arc::clone(&branch);
    thread::spawn(move || {
        *leaf.parent.write().unwrap() = Arc::downgrade(&branch);
    }).join().unwrap();
}

There's not one single designer you could email - while Rust started as a single person's project, that's definitely not the case any more!

If you want to discuss the design/implementation of the language, the best places to do so are on the internals forum or on the Rust Discord server.

1 Like

Please note that internals is not for asking questions about why Rust works the way it does, but for discussing the Rust compiler itself.

2 Likes

Note that you can get away with using the same name by defining the clone within a sub-scope:

thread::spawn({
    let leaf = Arc::clone(&leaf);
    move || {
        *leaf.parent.write().unwrap() = Arc::downgrade(&branch);
    }
}).join().unwrap();

Nope, the whole point of Rc and Arc is that they don't really copy memory when cloned, instead, they just increment a special counter (initially included / added to the allocated memory) :upside_down_face:

3 Likes

@Yandros @17cupsofcoffee Thank you for giving me some clues. I almost quit, and you brought me back.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.