>> Problem
I have a struct that contains other components:
struct ArithmeticLogicUnit;
struct ControlUnit;
struct Register;
struct CPU {
alu: ArithmeticLogicUnit,
cu: ControlUnit,
register: Register
}
impl Default for CPU {
fn default() -> Self {
Self {
alu: ArithmeticLogicUnit,
cu: ControlUnit,
register: Register
}
}
}
I want to test a function execute
which changes each component
state:
/// Main function to test
impl CPU {
fn execute(&mut self, instructions: &str) -> bool { todo!() }
}
/// Functions to check side-effects which may require mutable references
impl ArithmeticLogicUnit {
fn check(&mut self, register: &mut Register ) -> bool { true }
}
impl ControlUnit {
fn check(&mut self, register: &mut Register ) -> bool { false }
}
#[cfg(tests)]
mod tests {
#[test]
fn execute_should_work() {
let mut cpu = CPU::default();
assert!(cpu.execute("RANDOM CPU INSTRUCTIONS"));
// TODO: Assert ALU, CU and Register side-effects
}
}
To test each component state directly, I need to expose mutable access to those components since some components depend on each other. The easiest way to achieve this is to change the visibility of the struct fields from private to public:
/// Add `pub` for each field
struct CPU {
pub alu: ArithmeticLogicUnit,
pub cu: ControlUnit,
pub register: Register
}
let mut cpu = CPU::default();
assert!(cpu.execute("BLARB"));
let CPU { ref mut alu, ref mut cu, ref mut register } = &mut cpu;
assert!(alu.check(register));
assert!(cu.check(register));
However, this unnecessarily exposes the internal fields outside of a test. As an alternative, I could expose getter methods for each the components:
impl CPU {
pub fn alu(&self) -> &ArithmeticLogicUnit {
' &self.alu
}
pub fn alu_mut(&mut self) -> &mut ArithmeticLogicUnit {
&mut self.alu
}
pub fn cu(&self) -> &ControlUnit {
&self.cu
}
pub fn cu_mut(&mut self) -> &mut ControlUnit {
&mut self.cu
}
pub fn register(&self) -> &Register {
&self.register
}
pub fn register_mut(&mut self) -> &mut Register {
&mut self.register
}
}
Borrowing immutable references simultaneously works but naturally fails for mutable references:
/// Immutable references works
let cpu = CPU::default();
let alu = cpu.alu();
let cu = cpu.cu();
let register = cpu.register();
/// Mutable references does not since `cpu` is exclusively borrowed for
/// each component
let mut cpu = CPU::default();
let mut alu = cpu.alu_mut();
let mut cu = cpu.cu_mut();
let mut register = cpu.register_mut();
assert!(alu.check(register));
assert!(cu.check(register));
While the borrow checker correctly prevents mutably borrowing the same variable, how can we borrow multiple fields at the same time without reaching for unsafe?
>> Solution
Taking inspiration from Vec::split_at_mut, the unconventional yet slightly verbose answer is to mutable borrow everything required and return it as a struct which combines the two ideas:
/// Combining the first and second solution
struct CpuComponents<'a> {
pub alu: &'a ArithmeticLogicUnit,
pub cu: &'a ControlUnit,
pub register: &'a Register,
}
struct CpuComponentsMut<'a> {
pub alu: &'a mut ArithmeticLogicUnit,
pub cu: &'a mut ControlUnit,
pub register: &'a mut Register,
}
impl CPU {
pub fn components<'a>(&'a self) -> CpuComponents<'a> {
CpuComponents {
alu: &self.alu,
cu: &self.cu,
register: &self.register
}
}
pub fn components_mut<'a>(&'a mut self) -> CpuComponentsMut<'a> {
CpuComponentsMut {
alu: &mut self.alu,
cu: &mut self.cu,
register: &mut self.register
}
}
}
Although it seems strange to create a custom data type just to satisfy the borrow checker, Rust has smart pointers such as RefCell to enforce borrows rules at runtime. Nonetheless, the test can now work naturally.
let mut cpu = CPU::default();
let CpuComponentsMut { mut alu, mut cu, mut register } = cpu.components_mut();
assert!(alu.check(register));
assert!(cu.check(register));
If structs are a bit verbose, tuples can also work but be wary that it breaks when adding or changing it:
impl CPU {
pub fn components<'a>(&'a self) -> (&'a ArithmeticLogicUnit, &'a ControlUnit, &'a Register) {
(&self.alu, &self.cu, &self.register)
}
pub fn components_mut<'a>(&'a mut self) -> (&'a mut ArithmeticLogicUnit, &'a mut ControlUnit, &'a mut Register) {
(&mut self.alu, &mut self.cu, &mut self.register)
}
}
let (mut alu, mut cu, mut register) = cpu.components_mut();
Although my example may seem valid within a test, exposing internals should be avoided and perhaps refactoring or new methods may be needed instead. Overall while this perhaps incurs a slight compiler or code overhead, maintaining the safety guarantees is worth it with a little more work.