2
\$\begingroup\$

Needs

  • A container holds two collections: Parent instances and Child instances; their items can be searched by name;
  • A child can return a reference to its parent;
  • A parent can search its children by name and return one of them by reference;
  • A Child is read-only after creation, but new children or parents can be added at runtime;

Implementation

To do it, I choose to use Rc<RefCell<...>> and Weak<RefCell<...>> because I didn't find any other decent solution (unless maybe raw pointers):

  • in the container I store strong references to Parent and Child instances in order to be sure they will be alive during all the container's life,
  • and other references (Parent->Child and Child->Parent) are stored as weak references, in order to avoid cyclic references.

Questions

  • Concerning the way of handling references, is there a better way to do it?

  • Assuming that no reference at all will be kept outside the container's scope, is there a way to simplify it?
    Indeed, by looking at the last test, you can see that readability is getting a bit hard, just for a simple request like "Give me the name of ...". And beyond readability, I wonder if this does not have a runtime cost that could be avoided.

Note: The code could be improved by using HashMap instead of Vec, but my question is not dealing about this.

#![allow(dead_code)]

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

#[derive(Debug)]
struct Child {
    name: String,
    parent: Weak<RefCell<Parent>>,
}

impl Child {
    fn new(name: &str, parent: &Rc<RefCell<Parent>>) -> Child {
        Child {
            name: name.to_string(),
            parent: Rc::downgrade(parent),
        }
    }

    fn parent(&self) -> &Weak<RefCell<Parent>> {
        &self.parent
    }

    fn strong_parent(&self) -> Rc<RefCell<Parent>> {
        // As far as I understand, unwrap() should never panic as long as the container is still
        // alive:
        self.parent.upgrade().unwrap()
    }
}

#[derive(Debug)]
struct Parent {
    name: String,
    children: Vec<Weak<RefCell<Child>>>,
}

impl Parent {
    fn new(name: &str) -> Parent {
        Parent {
            name: name.to_string(),
            children: vec![],
        }
    }

    fn add_child(&mut self, child: &Rc<RefCell<Child>>) {
        self.children.push(Rc::downgrade(child));
    }

    fn child(&self, name: &str) -> Option<&Weak<RefCell<Child>>> {
        self.children.iter()
            .find(|c| c.upgrade().unwrap().borrow().name == name)
    }

    fn strong_child(&self, name: &str) -> Option<Rc<RefCell<Child>>> {
        self.children.iter()
            .map(|c| c.upgrade().unwrap())
            .find(|c| c.borrow().name == name)
    }
}

#[derive(Debug)]
struct Container {
    children: Vec<Rc<RefCell<Child>>>,
    parents:  Vec<Rc<RefCell<Parent>>>,
}

impl Container {
    pub fn new() -> Container {
        Container {
            children: vec![],
            parents: vec![],
        }
    }

    pub fn add_child(&mut self, name: &str, parent: &Rc<RefCell<Parent>>) -> &Rc<RefCell<Child>> {
        if self.child(name).is_some() {
            panic!(format!("Child '{}' already exists.", name));
        }
        let child = Rc::new(RefCell::new(Child::new(name, parent)));
        parent.borrow_mut().add_child(&child);
        self.children.push(child);
        self.children.last().unwrap()
    }

    pub fn add_parent(&mut self, name: &str) -> &Rc<RefCell<Parent>> {
        if self.parent(name).is_some() {
            panic!(format!("Parent '{}' already exists.", name));
        }
        let parent = Rc::new(RefCell::new(Parent::new(name)));
        self.parents.push(parent);
        self.parents.last().unwrap()
    }

    pub fn parent(&self, name: &str) -> Option<&Rc<RefCell<Parent>>> {
        self.parents.iter()
            .find(|p| p.borrow().name == name)
    }

    pub fn child(&self, name: &str) -> Option<&Rc<RefCell<Child>>> {
        self.children.iter()
            .find(|c| c.borrow().name == name)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[allow(unused_variables)]
    fn simple_test() {
        let mut container = Container::new();
        let p1 = container.add_parent("parent1").clone();
        let p1_c1 = container.add_child("parent1-child1", &p1).clone();
        let p1_c2 = container.add_child("parent1-child2", &p1).clone();

        // Here are tests as well as examples of how I would you use this code:

        // Get the parent of parent1-child1;
        assert_eq!(p1_c1.borrow().strong_parent().borrow().name, "parent1");

        // Get a child from parent1:
        assert_eq!(
            p1.borrow().strong_child("parent1-child2").unwrap().borrow().name,
            "parent1-child2"
        );
    }
}
```
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Since the child is read-only after creation, couldn't you use Child instead of RefCell<Child>?

\$\endgroup\$
1
  • \$\begingroup\$ This is a nice and appropriate simplication, thank you \$\endgroup\$
    – yolenoyer
    Commented Jun 4, 2020 at 7:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.