使用由简单结构共享的多个引用的惯用方法



我想有一个 在给定结构上运行的函数集。两个 CPU 需要访问 一个可变内存。 从我所读到的内容来看,我需要Rc<RefCell<_>>. 这类似于表示一棵树的问题 但它更简单,因为我的结构在编译时定义了关系,不需要是泛型的。

下面的代码有效,但我想知道它是否是惯用语。 来自OOP背景,我觉得它很有问题:

  1. 尽管memory字段存在于Cpu结构中,但我必须 每次我需要在borrow_mut()impl.这是因为 Rust 希望我避免两个 同时可变引用?

  2. 我必须将mem参数传递给do_stuff功能。如果我有几十个嵌套函数,我将不得不传递 每次调用时该参数。有可能避免这种情况吗?

  3. 如果我传递该参数,那么它就会变得同样简单 在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 硬件专家,可能没有使用完全正确的术语。

  1. 我必须将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())只会在运行时崩溃。