1

While porting an application from Python to Rust I ran into this problem.

If we have classes like this:

class Backend:
    def __init__(self, result_consumer=None):
        self.result_consumer = ResultConsumer(self, ..)
    
class ResultConsumer:
    def __init__(self, backend=None):
        self.backend=backend

and ResultConsumer is regularly calling methods like self.backend.method() and Backend is also calling methods like self.result_consumer.method().

In Rust you can't just use struct fields to establish such a relationship.

I tried an approach with generics (as ResultConsumer should support multiple backends)

struct ResultConsumer<B: Backend> {
    backend: Arc<B>,
}
struct Backend {
    result_consumer: ResultConsumer<Self>
}

what would be the most idiomatic way to represent this relationship in Rust? Is my example approach viable?

I am asking specifically, because while it compiles I cannot figure out how to populate the ResultConsumer in the result_consumer field for Backend when I construct it.

Herohtar
  • 5,347
  • 4
  • 31
  • 41
ss7
  • 2,902
  • 7
  • 41
  • 90
  • 2
    use `Rc` or just avoid this kind of pattern – Stargateur Sep 28 '21 at 04:05
  • Related: [How do I express mutually recursive data structures in safe Rust?](https://stackoverflow.com/questions/36167160/how-do-i-express-mutually-recursive-data-structures-in-safe-rust) – Sven Marnach Sep 28 '21 at 09:39

1 Answers1

1

If I understand you well, you can use something like the following

struct ResultConsumer {
    backend: RefCell<Weak<Backend>>,
}
struct Backend {
    result_consumer: RefCell<Rc<ResultConsumer>>,
}

impl Backend {
    fn new() -> Rc<Backend> {
        println!("Backend::new");
        let mut b_rc = Rc::new(Backend {
            result_consumer: RefCell::new(Rc::new(ResultConsumer {
                backend: RefCell::new(Weak::new()),
            })),
        });

        *b_rc.result_consumer.borrow().backend.borrow_mut() = Rc::downgrade(&b_rc);
        b_rc
    }
}
asmmo
  • 6,922
  • 1
  • 11
  • 25
  • 4
    One of the `Rc` should probably be a `Weak` to break the reference cycle. – Jmb Sep 28 '21 at 06:56
  • 4
    No it doesn't, with your code `drop (Backend::new())` will leak: the backend is not freed because the consumer points to it, and the consumer is not freed because the backend points to it. [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ac9dbff67631ece3f3b56eab87eaf8e6) – Jmb Sep 28 '21 at 07:24
  • @Jmb thank u. edited – asmmo Sep 28 '21 at 21:51