ajlajlajl
ajlajlajl

Reputation: 187

having two struct reference each other - rust

I'm quite new to Rust programming, and I'm trying to convert a code that I had in js to Rust.

A plain concept of it is as below:

fn main() {
    let mut ds=DataSource::new();
    let mut pp =Processor::new(&mut ds);
}

struct DataSource {
    st2r: Option<&Processor>,
}

struct Processor {
    st1r: &DataSource,
}

impl DataSource {
    pub fn new() -> Self {
        DataSource {
            st2r: None,
        }
    }
}

impl Processor {
    pub fn new(ds: &mut DataSource) -> Self {
        let pp = Processor {
            st1r: ds,
        };
        ds.st2r = Some(&pp);
        pp
    }
}

As you can see I have two main modules in my system that are inter-connected to each other and I need a reference of each in another.

Well, this code would complain about lifetimes and such stuff, of course 😑. So I started throwing lifetime specifiers around like a madman and even after all that, it still complains that in "Processor::new" I can't return something that has been borrowed. Legit. But I can't find any solution around it! No matter how I try to handle the referencing of each other, it ends with this borrowing error.

So, can anyone point out a solution for this situation? Is my app's structure not valid in Rust and I should do it in another way? or there's a trick to this that my inexperienced mind can't find?

Thanks.

Upvotes: 2

Views: 1553

Answers (1)

Jmb
Jmb

Reputation: 23453

What you're trying to do can't be expressed with references and lifetimes because:

  • The DataSource must live longer than the Processor so that pp.st1r is guaranteed to be valid,
  • and the Processor must live longer than the DataSource so that ds.st2r is guaranteed to be valid. You might think that since ds.st2r is an Option and since the None variant doesn't contain a reference this allows a DataSource with a None value in st2r to outlive any Processors, but unfortunately the compiler can't know at compile-time whether st2r contains Some value, and therefore must assume it does.

Your problem is compounded by the fact that you need a mutable reference to the DataSource so that you can set its st2r field at a time when you also have an immutable outstanding reference inside the Processor, which Rust won't allow.

You can make your code work by switching to dynamic lifetime and mutability tracking using Rc (for dynamic lifetime tracking) and RefCell (for dynamic mutability tracking):

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

fn main() {
    let ds = Rc::new (RefCell::new (DataSource::new()));
    let pp = Processor::new (Rc::clone (&ds));
}

struct DataSource {
    st2r: Weak<Processor>,
}

struct Processor {
    st1r: Rc<RefCell<DataSource>>,
}

impl DataSource {
    pub fn new() -> Self {
        DataSource {
            st2r: Weak::new(),
        }
    }
}

impl Processor {
    pub fn new(ds: Rc::<RefCell::<DataSource>>) -> Rc<Self> {
        let pp = Rc::new (Processor {
            st1r: ds,
        });
        pp.st1r.borrow_mut().st2r = Rc::downgrade (&pp);
        pp
    }
}

Playground

Note that I've replaced your Option<&Processor> with a Weak<Processor>. It would be possible to use an Option<Rc<Processor>> but this would risk leaking memory if you dropped all references to DataSource without setting st2r to None first. The Weak<Processor> behaves more or less like an Option<Rc<Processor>> that is set to None automatically when all other references are dropped, ensuring that memory will be freed properly.

Upvotes: 2

Related Questions