我想有一个 在给定结构上运行的函数集。两个 CPU 需要访问 一个可变内存。 从我所读到的内容来看,我需要Rc<RefCell<_>>
. 这类似于表示一棵树的问题 但它更简单,因为我的结构在编译时定义了关系,不需要是泛型的。
下面的代码有效,但我想知道它是否是惯用语。 来自OOP背景,我觉得它很有问题:
-
尽管
memory
字段存在于Cpu
结构中,但我必须 每次我需要在borrow_mut()
impl
.这是因为 Rust 希望我避免两个 同时可变引用? -
我必须将
mem
参数传递给do_stuff
功能。如果我有几十个嵌套函数,我将不得不传递 每次调用时该参数。有可能避免这种情况吗? -
如果我传递该参数,那么它就会变得同样简单 在
Cpu
结构中没有内存字段,而只是传递mut &Memory
参考资料,而是质疑是否需要Rc<RefCell<Memory>>
构造...
代码示例:
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]);
}
我需要在
impl
中访问它时,我都必须对其应用borrow_mut()
。这是因为 Rust 希望我避免同时使用两个可变引用吗?
是的。这里有一个与物理硬件的简洁类比:您的两个处理器必须具有某种机制,以避免同时访问内存总线而导致的冲突。在这个类比中,使用RefCell
是一个总线仲裁器*:它看到请求并授予或拒绝它们,以确保一次只有一个访问权限。您确定的替代方案,即向每个调用传递&mut Memory
,就像有一个时钟循环,为每个 CPU 提供内存访问的非重叠时间片。
第二种选择通常被认为是更好、更惯用的 Rust,当它实用时。它是静态检查的,避免了更新RefCell
内的借用标志的开销。它的主要缺点是它要求调用方传入正确的&mut
引用——这在像您这样简单且紧密耦合的情况下不是问题。
在这种情况下,您有一个简单的整数数组,第三种选择是从真实 CPU 的角度模拟更接近内存工作方式的东西:将您的内存存储为[Cell<u8>; 5]
或[AtomicU8; 5]
,以便允许每个可寻址位置随时独立修改,即使您有一个非排他性的&
引用而不是&mut
。这允许比传递&mut Memory
更复杂的代码结构,但不需要像使用RefCell
那样显式借用/锁定整个内存。它为您提供针对冲突突变的最少静态或动态检查,但如果您打算实现的操作与该检查不完全一致,则可能是合适的。
* 我不是 CPU 硬件专家,可能没有使用完全正确的术语。
- 我必须将mem参数传递给do_stuff函数。如果我有几十个嵌套函数,我必须在每次调用时传递该参数。有可能避免这种情况吗?
您可以通过引入一种类型来避免这种情况,该类型既保存对内存的可变引用,又定义行为:
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);
}
}
您的Cpu
可以定义一个获取内存的实用程序函数:
fn mem(mem: &mut Memory) -> CpuMem {
CpuMem { mem }
}
之后,run()
可以如下所示:
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();
}
如果do_stuff()
和其他需要从Cpu
访问某些字段,您也可以在CpuMem
中引用这些其他内容。您也可以在此处引用 CPU 本身,但是调用借用内存的函数(例如run()
)只会在运行时崩溃。