I am implementing an implementation of the Command Pattern where a subscriber would receive data from a reader thread and I have issues testing my implementation using Rust mocks. Consider this stripped down example:
use std::thread;
pub trait Subscriber: Sync + Send {
fn callback(&self);
}
pub struct Publisher {
thread_handler: Option<thread::JoinHandle<()>>,
}
impl Publisher {
pub fn new() -> Self {
Self {
thread_handler: None,
}
}
pub fn start(&mut self, sub: Box<dyn Subscriber>) {
self.thread_handler = Some(thread::spawn(move || {
sub.callback();
}));
}
}
#[cfg(test)]
mod tests {
use super::*;
use mockall::*;
mock! {
TestSubscriber {}
impl Subscriber for TestSubscriber {
fn callback(&self);
}
}
#[test]
fn test_subscriber() {
let mut publisher = Publisher::new();
let mut mock_test_subscriber = Box::new(MockTestSubscriber::new());
publisher.start(mock_test_subscriber);
mock_test_subscriber.expect_callback().times(1);
thread::sleep(std::time::Duration::from_millis(10));
}
}
This throws
error[E0382]: borrow of moved value: `mock_test_subscriber`
--> src/foo.rs:46:9
|
42 | let mut mock_test_subscriber = Box::new(MockTestSubscriber::new());
| ------------------------ move occurs because `mock_test_subscriber` has type `Box<MockTestSubscriber>`, which does not implement the `Copy` trait
43 |
44 | publisher.start(mock_test_subscriber);
| -------------------- value moved here
45 |
46 | mock_test_subscriber.expect_callback().times(1);
| ^^^^^^^^^^^^^^^^^^^^ value borrowed here after move
I don't really know how to solve this, I tried wrapping the dyn Subscriber
around Arc
but still couldn't make it work. Any suggestions would be much appreciated. Or maybe there is a better design to achieve the same result.
You were on the right track trying to use Arc
- it should be the first thing to try when doing ad-hoc multi-threading. Here since you are trying to share mock_test_subscriber
across two threads, this is exactly what you need. So first we change the signature of Publisher::start
to use Arc
:
pub fn start(&mut self, sub: Arc<dyn Subscriber>) {
self.thread_handler = Some(thread::spawn(move || {
sub.callback();
}));
}
... and update the call site accordingly:
let mock_test_subscriber = Arc::new(MockTestSubscriber::new());
// create another reference by cloning the Arc
// then coerce the underlying concrete type into a trait object
publisher.start(Arc::clone(&mock_test_subscriber) as Arc<dyn Subscriber>);
Note that I removed the mut
on mock_test_subscriber
because it's probably not what you mean. Writing let mut foo = Arc::new(T);
merely allows you to set foo
to another Arc
(i.e. point to something else), not modifying its underlying value. Arc
is semantically a shared reference and therefore cannot be mutable due to Rust's rule on mutable aliasing. Syntactically, this is expressed as Arc
not implementing DerefMut
.
To obtain a mutable reference to the underlying value, use Arc<Mutex<T>>
or Arc<RwLock<T>>
. Mutex
and RwLock
are able to safely provide mutable references through shared references by internally using the correct synchronisation primitives to ensure exclusive access.