首页 > 解决方案 > 如果我想将单个可变对象传递给函数的多个参数,我应该怎么做?

问题描述

我用 Rust 编写了一个使用步进电机播放音乐的程序,现在我想添加一些假对象,以便我可以进行自动化测试。但是,我不知道以我的程序可以实际使用它们的方式定义这些假对象的好方法。

你可以在 Rust Playground 上运行我的代码

起作用的部分

程序的主循环使用两个特征对象。一个对象实现了一个称为 的特征Timer,它表示一个计时器,可用于使事情定期发生。另一个对象实现了一个名为 的特征Motor,并表示步进电机本身。这些特征的定义在我的 Rust Playground 帖子中。

主循环只需等待 500 微秒,将“步进”引脚拉高,再等待 500 微秒,将“步进”引脚拉低,并重复总共 1,000 个周期。这会导致电机在一秒钟内每秒步进 1,000 次。

fn spin<T: Timer, M: Motor>(timer: &mut T, motor: &mut M) {
    timer.reset();
    for _ in 0..1000 {
        timer.wait_microseconds(500);
        motor.set_step_high();
        timer.wait_microseconds(500);
        motor.set_step_low();
    }
}

到目前为止,一切都很好。我(在我的真实代码中,而不是 Rust Playground 帖子中)有一个工作实现Timer,一个工作实现Motor,这个spin函数使电机旋转,听起来很漂亮。

有问题的部分

我希望能够进行自动化测试,所以我编写了一个“假”对象,它MotorTimer一种对测试有用的方式实现。类型本身只是一个结构:

/// A mock timer and motor which simply tracks the amount of simulated time that
/// the motor driver has its "step" pin pulled HIGH.
struct DummyTimerMotor {
    is_on: bool,
    time_high: u64,
}

set_step_highand的实现set_step_low只是设置is_ontrueand false(分别),而 的实现wait_microseconds只是检查是否is_ontrue,如果是,则添加给定的时间量time_high。这些实现在我的 Rust Playground 帖子中。

天真地,我希望能够将一个DummyTimerMotor对象作为 的两个参数传递spin,然后再查看time_high,发现它的值为 500000。但是,这当然是不允许的:

fn main() {
    let mut dummy: DummyTimerMotor = DummyTimerMotor {
        is_on: false, time_high: 0
    };
    
    spin(&mut dummy, &mut dummy); // Oops, not allowed!
    
    print!("The 'step' pin was HIGH for {} microseconds", dummy.time_high);
}

这给出了一条错误消息:“不能dummy一次多次借用 mutable。”

我确切地知道为什么我会收到该错误消息,这是有道理的。获得我想要获得的行为的好方法是什么?

我只有一个合理的想法:改变spin,而不是采用一个实现的对象Timer和另一个实现的对象Motor,而是采用一个同时实现Timer和的对象Motor。然而,这对我来说似乎不优雅(作为一个 Rust 新手)。从概念上讲,计时器是一回事,电机是另一回事。spin拿一个既是计时器又是电机的物体是非常不直观的。看起来我不应该spin仅仅为了适应定时器和电机的实现细节而改变实现方式。


完整的代码清单

如果 Rust Playground 出现故障,下面是我在那里的整个程序,以及整个错误输出。

/// A timer which can be used to make things happen at regular intervals.
trait Timer {
    /// Set the timer's reference time to the current time.
    fn reset(&mut self);
    /// Advance the timer's reference time by the given number of microseconds,
    /// then wait until the reference time.
    fn wait_microseconds(&mut self, duration: u64);
}

/// The interface to a stepper motor driver.
trait Motor {
    /// Pull the "step" pin HIGH, thereby asking the motor driver to move the
    /// motor by one step.
    fn set_step_high(&mut self);
    /// Pull the "step" pin LOW, in preparation for pulling it HIGH again.
    fn set_step_low(&mut self);
}

fn spin<T: Timer, M: Motor>(timer: &mut T, motor: &mut M) {
    timer.reset();
    for _ in 0..1000 {
        timer.wait_microseconds(500);
        motor.set_step_high();
        timer.wait_microseconds(500);
        motor.set_step_low();
    }
}

/// A mock timer and motor which simply tracks the amount of simulated time that
/// the motor driver has its "step" pin pulled HIGH.
struct DummyTimerMotor {
    is_on: bool,
    time_high: u64,
}

impl Timer for DummyTimerMotor {
    fn reset(&mut self) { }
    
    fn wait_microseconds(&mut self, duration: u64) {
        if self.is_on {
            self.time_high += duration;
        }
    }
}

impl Motor for DummyTimerMotor {
    fn set_step_high(&mut self) {
        self.is_on = true;
    }
    
    fn set_step_low(&mut self) {
        self.is_on = false;
    }
}

fn main() {
    let mut dummy: DummyTimerMotor = DummyTimerMotor {
        is_on: false, time_high: 0
    };
    
    spin(&mut dummy, &mut dummy); // Oops, not allowed!
    
    print!("The 'step' pin was HIGH for {} microseconds", dummy.time_high);
}
error[E0499]: cannot borrow `dummy` as mutable more than once at a time
  --> src/main.rs:61:22
   |
61 |     spin(&mut dummy, &mut dummy); // Oops, not allowed!
   |     ---- ----------  ^^^^^^^^^^ second mutable borrow occurs here
   |     |    |
   |     |    first mutable borrow occurs here
   |     first borrow later used by call

标签: rustborrow-checkermutability

解决方案


您可以通过以下方式拥有不同的DummyTimerDummyMotor谁共享状态Rc<RefCell<State>>

struct State {
    is_on: bool,
    time_high: u64,
}

struct DummyTimer {
    state: Rc<RefCell<State>>,
}

impl Timer for DummyTimer {
    fn reset(&mut self) { }
    
    fn wait_microseconds(&mut self, duration: u64) {
        let mut t = self.state.borrow_mut();
        if t.is_on {
            t.time_high += duration;
        }
    }
}

struct DummyMotor {
    state: Rc<RefCell<State>>,
}

impl Motor for DummyMotor {
    fn set_step_high(&mut self) {
        self.state.borrow_mut().is_on = true;
    }
    
    fn set_step_low(&mut self) {
        self.state.borrow_mut().is_on = false;
    }
}

fn main() {
    let state = Rc::new (RefCell::new (State { is_on: false, time_high: 0, }));
    let mut motor = DummyMotor { state: Rc::clone (&state), };
    let mut timer = DummyTimer { state: Rc::clone (&state), };

    spin(&mut timer, &mut motor); // Now it's allowed
    
    print!("The 'step' pin was HIGH for {} microseconds", state.borrow().time_high);
}

操场


推荐阅读