Rust idiomatic way of wiring objects


#1

Hi,

I would like to know the idiomatic way of initializing a tree of objects with dependencies. Ideally I would like to pass dependencies in constructor as references. The problem I am facing is illustrated with the following code sample.

I have seen the questions similar to this but I would like to understand if there is a different way I should be approaching the problem.

Thanks
Andre

struct Dep1 {}
struct Dep2<'a> {
    dep1: &'a Dep1,
}
struct Top<'a> {
    dep1: Dep1,
    dep2: Dep2<'a>,
}

impl<'a> Top<'a> {
    pub fn new() -> Top<'a> {
        Top {
            dep1: Dep1 {},
            dep2: Dep2 { dep1: &dep1 }
        }
    }
}

fn main() {
    let top = Top::new();
}

(Playground)

Errors:

   Compiling playground v0.0.1 (file:///playground)
error[E0425]: cannot find value `dep1` in this scope
  --> src/main.rs:14:33
   |
14 |             dep2: Dep2 { dep1: &dep1 }
   |                                 ^^^^
   |                                 |
   |                                 `self` value is only available in methods with `self` parameter
   |                                 help: try: `self.dep1`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0425`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.


#2

You’re attempting to create a self-referential struct because Dep2, owned by Top, is trying to reference Dep1, which is also owned by Top - this essentially means Top has references to itself.

If you want shared ownership, you can look into Rc. Otherwise, decide who’s going to be the unique owner of a given value and structure the code accordingly (i.e. that owner creates, or receives, the value, and provides some accessors to it to the outside world).

Getting this structure/organization right is probably one of the harder tasks if one is coming from a GC language, where cycles in the graph are super easy to create.


#3

As Vitaly explains, structs with a reference to their parent will not work in Rust at all. No amount of lifetime annotations can ever fix it. It’s a completely forbidden construct, because it’s unsafe in non-obvious ways.

I’d add that you should avoid structs with references, period. There are some specific useful cases for structs with references, mostly performance optimizations and temporary views into other structures, but for a novice users they are going to feel infuriatingly limited and cause no end of fighting with the borrow checker.

I recommend using only owned values (no &, only Box/Rc/Arc and other “Capital-letter” types like Vec, String and full copies of your own structs) in structs. In your case if you use in structs Arc<Mutex<Dep1>> (or Rc<RefCell<Dep1>> which is the same thing for single-threaded programs), instead of &Dep1, it will work in a way you’d expect from garbage-collected types.

Idiomatic Rust solution to this is to avoid going full OOP-style with this and rely more on passing required objects as function arguments (dep2.do_something_with(&dep1)). References in functions arguments are generally easy to work with and behave as you’d expect.


#4

Thanks a lot for the replies. I was trying to avoid the Rc RefCell combo as I thought the simplest cases would be able to model with struct holding references.