Reputation: 161
I want to have a
set of functions to operate on a given structure. Two CPUs need to access
one single mutable memory.
From what I've read, I need Rc<RefCell<_>>
.
This is akin to the problem of the representing a tree
but it is simpler since my structs have relationships defined at compile time and don't need to be generic.
The code below works, but I wonder if it is idiomatic. Coming from OOP background, I find it quite problematic:
Although the memory
field is present in the Cpu
struct, I have to
apply a borrow_mut()
on it each time I need to access it in the
impl
. Is this because Rust wants me to avoid two
simultaneous mutable references?
I have to pass the mem
parameter to the do_stuff
function. If I have dozens of nested functions I'll have to pass
that parameter on each call. Is it possible to avoid that?
If I pass that parameter around, then it'd be just as simple
to not have the memory field in the Cpu
struct, and just passing
mut &Memory
references around instead which questions the need for the
Rc<RefCell<Memory>>
construct...
Code sample:
use std::cell::{RefCell, RefMut};
use std::rc::Rc;
struct Cpu {
memory: Rc<RefCell<Memory>>
}
impl Cpu {
fn new(m : Rc<RefCell<Memory>>) -> Cpu {
Cpu { memory: m } }
fn run(&mut self) {
self.do_stuff(self.memory.borrow_mut());
// Show repetition of borrow_mut (point 1)
self.do_stuff(self.memory.borrow_mut());
}
// Show the need for mem parameter (point 2)
fn do_stuff(&self, mut mem: RefMut<Memory>) {
let i = mem.load(4) + 10;
mem.set(4, i);
}
}
struct Memory { pub mem: [u8; 5] }
impl Memory {
fn new() -> Memory { Memory { mem: [0 as u8; 5] } }
fn load( &self, ndx : usize) -> u8 { self.mem[ndx] }
fn set( &mut self, ndx : usize, val: u8) { self.mem[ndx] = val }
}
fn main() {
let memory: Rc<RefCell<_>> = Rc::new(RefCell::new(Memory::new()));
let mut cpu1 = Cpu::new(memory.clone());
let mut cpu2 = Cpu::new(memory.clone());
cpu1.run();
cpu2.run();
println!("{}",memory.borrow().mem[4]);
}
Upvotes: 2
Views: 1732
Reputation: 155186
- I have to pass the mem parameter to the do_stuff function. If I have dozens of nested functions I'll have to pass that parameter on each call. Is it possible to avoid that?
You can avoid that by introducing a type that both holds the mutable reference to the memory and defines the behavior:
struct CpuMem<'a> {
mem: &'a mut Memory,
}
impl CpuMem<'_> {
fn do_stuff(&mut self) {
// can call other functions on self without passing an explicit `mem`
let i = self.mem.load(4) + 10;
self.mem.set(4, i);
}
}
Your Cpu
can define a utility function that obtains the memory:
fn mem(mem: &mut Memory) -> CpuMem {
CpuMem { mem }
}
after which run()
can look like this:
fn run(&mut self) {
let mut borrow = self.memory.borrow_mut();
let mut mem = Self::mem(borrow.deref_mut());
mem.do_stuff();
mem.do_stuff();
}
If do_stuff()
and other need to access some fields from Cpu
, you can put references to those other things in CpuMem
as well. You can also put a reference to the Cpu itself there, but then you calling a function that borrows memory, such as run()
, will simply panic at run-time.
Upvotes: 0
Reputation: 43842
I have to apply a
borrow_mut()
on it each time I need to access it in theimpl
. Is this because Rust wants me to avoid two simultaneous mutable references?
Yes. There's a tidy analogy with physical hardware, here: your two processors must have some mechanism to avoid conflicts resulting from accessing the memory bus at the same time. In this analogy, using RefCell
is a bus arbiter*: it sees the requests and grants or denies them to ensure there's only one access at a time. The alternative you have identified, passing an &mut Memory
to each call, is like there being a single clock which cycles through giving non-overlapping time-slices of the memory access to each CPU.
The second option is generally considered better and more idiomatic Rust, when it is practical. It is statically checked, avoiding the overhead of updating the borrow flags inside a RefCell
. Its main disadvantage is that it requires the caller to pass in the right &mut
reference — which is not a problem in straightforward and tightly-coupled situations like yours.
A third option, in this situation where you have a simple integer array, would be to simulate something closer to the way memory works from the perspective of the real CPU: store your memory as [Cell<u8>; 5]
or [AtomicU8; 5]
, so that each addressable location is allowed to be independently modified at any time, even if you have a non-exclusive &
reference to it rather than &mut
. This allows more complex code structures than passing around &mut Memory
, but does not require explicit borrowing/locking of the entire memory like using a RefCell
does. It offers you the least static or dynamic checking against conflicting mutations, but it might be appropriate if the operations you intend to implement are not well aligned with that checking.
* I'm not a CPU hardware expert and may not be using the exact right terms.
Upvotes: 3