I have a set of objects that need to know each other to cooperate. These objects are stored in a container. I'm trying to get a very simplistic idea of how to architecture my code in Rust.
Let's use an analogy. A Computer
contains:
- 1
Mmu
- 1
Ram
- 1
Processor
In Rust:
struct Computer {
mmu: Mmu,
ram: Ram,
cpu: Cpu,
}
For anything to work, the Cpu
needs to know about the Mmu
it is linked to, and the Mmu
needs to know the Ram
it is linked to.
I do not want the Cpu
to aggregate by value the Mmu
. Their lifetimes differ: the Mmu
can live its own life by itself. It just happens that I can plug it to the Cpu
. However, there is no sense in creating a Cpu
without an Mmu
attached to it, since it would not be able to do its job. The same relation exists between Mmu
and Ram
.
Therefore:
- A
Ram
can live by itself. - An
Mmu
needs aRam
. - A
Cpu
needs anMmu
.
How can I model that kind of design in Rust, one with a struct whose fields know about each other.
In C++, it would be along the lines of:
>
struct Ram
{
};
struct Mmu
{
Ram& ram;
Mmu(Ram& r) : ram(r) {}
};
struct Cpu
{
Mmu& mmu;
Cpu(Mmu& m) : mmu(m) {}
};
struct Computer
{
Ram ram;
Mmu mmu;
Cpu cpu;
Computer() : ram(), mmu(ram), cpu(mmu) {}
};
Here is how I started translating that in Rust:
struct Ram;
struct Mmu<'a> {
ram: &'a Ram,
}
struct Cpu<'a> {
mmu: &'a Mmu<'a>,
}
impl Ram {
fn new() -> Ram {
Ram
}
}
impl<'a> Mmu<'a> {
fn new(ram: &'a Ram) -> Mmu<'a> {
Mmu {
ram: ram
}
}
}
impl<'a> Cpu<'a> {
fn new(mmu: &'a Mmu) -> Cpu<'a> {
Cpu {
mmu: mmu,
}
}
}
fn main() {
let ram = Ram::new();
let mmu = Mmu::new(&ram);
let cpu = Cpu::new(&mmu);
}
That is fine and all, but now I just can't find a way to create the Computer
struct.
I started with:
struct Computer<'a> {
ram: Ram,
mmu: Mmu<'a>,
cpu: Cpu<'a>,
}
impl<'a> Computer<'a> {
fn new() -> Computer<'a> {
// Cannot do that, since struct fields are not accessible from the initializer
Computer {
ram: Ram::new(),
mmu: Mmu::new(&ram),
cpu: Cpu::new(&mmu),
}
// Of course cannot do that, since local variables won't live long enough
let ram = Ram::new();
let mmu = Mmu::new(&ram);
let cpu = Cpu::new(&mmu);
Computer {
ram: ram,
mmu: mmu,
cpu: cpu,
}
}
}
Okay, whatever, I won't be able to find a way to reference structure fields between them. I thought I could come up with something by creating the Ram
, Mmu
and Cpu
on the heap; and put that inside the struct:
struct Computer<'a> {
ram: Box<Ram>,
mmu: Box<Mmu<'a>>,
cpu: Box<Cpu<'a>>,
}
impl<'a> Computer<'a> {
fn new() -> Computer<'a> {
let ram = Box::new(Ram::new());
// V-- ERROR: reference must be valid for the lifetime 'a
let mmu = Box::new(Mmu::new(&*ram));
let cpu = Box::new(Cpu::new(&*mmu));
Computer {
ram: ram,
mmu: mmu,
cpu: cpu,
}
}
}
Yeah that's right, at this point in time Rust has no way to know that I'm going to transfer ownership of let ram = Box::new(Ram::new())
to the Computer
, so it will get a lifetime of 'a
.
I've been trying various more or less hackish ways to get that right, but I just can't come up with a clean solution. The closest I've come is to drop the reference and use an Option
, but then all my methods have to check whether the Option
is Some
or None
, which is rather ugly.
I think I'm just on the wrong track here, trying to map what I would do in C++ in Rust, but that doesn't work. That's why I would need help finding out what is the idiomatic Rust way of creating this architecture.