2

I have some nested structs and cannot create a back reference to the parent struct. An example:

struct Foo<'a> {
    parent: &'a Bar<'a>,
}

impl<'a> Foo<'a> {
    fn new(parent: &'a Bar) -> Self {
        Foo { parent: parent }
    }

    fn hello_world(&self) -> String {
        self.parent.hello().to_owned() + " world"
    }
}

struct Bar<'b> {
    child: Option<Foo<'b>>,
    data: &'static str,
}

impl<'b> Bar<'b> {
    fn new() -> Self {
        Bar {
            child: None,
            data: "hello",
        }
    }

    fn hello(&self) -> &str {
        self.data
    }

    fn get_foo(&self) -> Option<&Foo> {
        self.child.as_ref()
    }
}

fn main() {
    let bar = Bar::new();
    assert_eq!("hello", bar.hello());
    match bar.get_foo() {
        Some(foo) => assert_eq!("hello world", foo.hello_world()),
        None => (),
    }
}

How can I replace None with Some<Foo> with a reference to Bar? So far I'm not sure that it is possible.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Gedweb
  • 697
  • 1
  • 6
  • 22
  • 6
    You can't. See http://stackoverflow.com/q/32300132/155423; http://stackoverflow.com/q/28833622/155423; and lots of other questions about circular references. – Shepmaster Sep 26 '16 at 15:03
  • @Shepmaster, is that true? What am I missing in my example [here](https://play.rust-lang.org/?gist=ec859a25cc772183411a2dfb10258cbe&version=stable&backtrace=0)? When I add a line to debug print my `parent` object, I get a stack overflow error where it tries to print the circular references for parent/child... – Jacob Brown Sep 26 '16 at 20:37
  • Yes, it's true; I wouldn't deliberately lie to someone ^_^. A *reference* is `&Foo`. In the example in your comment, you have an `Arc`, which isn't a plain, boring reference, but a more intelligent *smart pointer*. However, you've created an infinite cycle (parent points to child points to parent points to ...). Generally, that's what [`Weak`](https://doc.rust-lang.org/std/sync/struct.Weak.html) references are for. – Shepmaster Sep 26 '16 at 20:43

2 Answers2

1

It's not exactly a drop-in solution for your example, but I believe you can create "circular references" as you specify using Arc and RwLock. The API is not exactly the same (e.g., parent is an optional field), I renamed some objects, and it is definitely more verbose, but your tests pass!

use std::sync::{Arc, RwLock};

#[derive(Debug, Clone)]
struct Child {
    parent: Option<Arc<RwLock<Parent>>>
}

impl Child {
    fn new() -> Self {
        Child {
            parent: None
        }
    }

    fn hello_world(&self) -> String {
        let x = self.parent.as_ref().unwrap().clone();
        let y = x.read().unwrap();
        y.hello().to_owned() + " world"
    }
}

#[derive(Debug, Clone)]
struct Parent {
    child: Option<Arc<RwLock<Child>>>,
    data: &'static str
}

impl Parent {
    fn new() -> Self {
        Parent {
            child: None,
            data: "hello"
        }
    }

    fn hello(&self) -> &str {
        self.data
    }

    fn get_child(&self) -> Option<Arc<RwLock<Child>>> {
        self.child.as_ref().map(|x| x.clone() )
    }


}

fn main() {
    let parent = Arc::new(RwLock::new(Parent::new()));
    let child = Arc::new(RwLock::new(Child::new()));

    parent.write().unwrap().child = Some(child.clone());
    child.write().unwrap().parent = Some(parent.clone());

    assert_eq!("hello", parent.read().unwrap().hello());

    {
        let x = parent.read().unwrap();
        match x.get_child() {
            Some(child) => { assert_eq!("hello world", child.read().unwrap().hello_world()); }
            None => {},
        }
    }

}
Jacob Brown
  • 7,221
  • 4
  • 30
  • 50
  • Ah, I completely missed that it wasn't OP with that comment, so I forgot to at-mention you. Make sure you check it out; especially the bit about `Weak` and the fact that this solution doesn't use **references**. – Shepmaster Sep 26 '16 at 21:07
  • @Shepmaster, oh, thanks! But isn't a cloned `Arc` a kind of reference, at least in a non-strict understanding of the word? Also, surely there are cases where you would want an "infinite cycle" of references (understanding of course that they are dangerous and using them in some ways can cause an SO)? – Jacob Brown Sep 26 '16 at 21:23
  • Yeah, it is a *kind* of reference (**A**tomically **R**eference **C**ounted), but if someone were to ask me how to do something with "references", I wouldn't expect to use an `Arc`. I'm not actually sure why someone would want a cycle of references. Note that a cycle will cause a memory leak when dropped. – Shepmaster Sep 26 '16 at 23:03
1

I have a similar problem, and am not entirely satisfied with the proposed solutions.

If your structure is really nested (i.e. you have a notion of "parent" and "child", with a unique parent for each child), then it seems natural that the parent should own the child(ren). So using Rc/Arc (which are designed to allow for multiple owners) does not look like the right solution -- all the less so because, as @Shepmaster points out, it "encourages" (or at least allows) the creation of cyclic references.

My idea was to have each child hold a raw pointer to its parent:

pub struct Node {
    parent: *mut Node,
    // ...
}

Since a node is owned by its parent, it can only be borrowed (resp. mutably borrowed) while its parent is being borrowed (resp. mutably borrowed). So in that context, it should be safe to cast self.parent to a &Node (resp. &mut Node, if self is mutable).

impl Node {
    pub fn get_parent(&self) -> Option<&Node> {
        unsafe { self.parent.as_ref() }
    }

    pub fn get_mut_parent(&mut self) -> Option<&mut Node> {
        unsafe { self.parent.as_mut() }
    }
}

However, this requires that the address of the parent node never changes (i.e. the parent is never moved). This can be achieved by only ever handling boxed nodes.

pub struct Node {
    parent: *mut Node,
    children: Vec<Box<Node>>,
    // ..
}

impl Node {
    pub fn new(data: &str) -> Box<Node> {
        Box::new(Node {
            parent: std::ptr::null_mut(),
            children: vec![],
            // ..
        })
    }

    pub fn append_child(&mut self, mut child: Box<Node>) -> usize {
        child.parent = self;
        self.children.push(child);
        self.children.len() - 1
    }
}

I implemented a full-fledged example in the playground.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Pierre-Antoine
  • 1,915
  • 16
  • 25
  • Here is a variant using `std::ptr::NonNull` instead of a raw pointer: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5c90c70d097e51bb1fcae03e90ea50ea . Not sure what the difference is, but the documentation of `NonNull` says "This is often the correct thing to use when building data structures using raw pointers". – Pierre-Antoine Dec 15 '18 at 16:12
  • 1
    Your children functions can be `self.children.get_mut(i).map(|x| &mut **x)`. – Shepmaster Dec 16 '18 at 01:24
  • 1
    Idiomatic Rust naming suggest that the function names would be `foo_bar_mut` not `foo_mut_bar`; The `get_` prefix is usually not used for methods. – Shepmaster Dec 16 '18 at 01:25
  • 1
    I think your find function's `else` clause can be `self.children.iter_mut().flat_map(|c| c.find_mut(data)).next()`. – Shepmaster Dec 16 '18 at 01:28
  • Thanks @Shepmaster for these advices. A more idiomatic version is here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=207113d3fa0108a8a8ddad444ecbf689 – Pierre-Antoine Dec 16 '18 at 14:29