Reputation: 187
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
Reputation: 23453
What you're trying to do can't be expressed with references and lifetimes because:
DataSource
must live longer than the Processor
so that pp.st1r
is guaranteed to be valid,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 Processor
s, 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
}
}
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