Rust线程化发布者的Mock订阅者测试报错(E0382)求助
Rust线程化发布者的Mock订阅者测试报错(E0382)求助
我来帮你分析下这个问题,你遇到的E0382错误本质是所有权转移导致的无效访问:当你调用publisher.start(mock_test_subscriber)时,这个Box包裹的Mock实例被完整地move到了新创建的线程里,主线程的mock_test_subscriber变量已经彻底失去了对这个实例的所有权,之后再尝试调用它的expect_callback()自然会报错。
下面给你两个可行的解决方案,都是围绕共享所有权和线程安全的内部可变性来解决问题:
方案一:用Arc+Mutex共享Mock实例(不修改原业务代码)
我们可以在测试代码里给Arc<Mutex<MockTestSubscriber>>实现Subscriber trait,这样既能在线程间共享Mock的所有权,又能通过Mutex保证线程安全的可变访问(因为Mock的expect方法需要修改内部状态)。
修改后的测试代码如下:
#[cfg(test)] mod tests { use super::*; use mockall::*; use std::sync::{Arc, Mutex}; mock! { TestSubscriber {} impl Subscriber for TestSubscriber { fn callback(&self); } } // 为Arc<Mutex<MockTestSubscriber>>实现Subscriber trait impl Subscriber for Arc<Mutex<MockTestSubscriber>> { fn callback(&self) { // 锁定Mutex并调用内部Mock的callback self.lock().unwrap().callback(); } } #[test] fn test_subscriber() { let mut publisher = Publisher::new(); // 创建带共享所有权和线程安全可变性的Mock实例 let mock_subscriber = Arc::new(Mutex::new(MockTestSubscriber::new())); // 先设置调用期望(此时我们还持有Arc的引用) mock_subscriber.lock().unwrap().expect_callback().times(1); // 克隆Arc,把克隆后的实例move进线程 let mock_clone = mock_subscriber.clone(); publisher.start(Box::new(mock_clone)); // 不要用sleep等待,直接join线程确保执行完成 if let Some(handle) = publisher.thread_handler.take() { handle.join().unwrap(); } } }
方案二:调整Publisher的设计(更适合多线程场景)
如果你的业务场景本身就需要订阅者被多线程共享,不妨直接修改Publisher的start方法,让它接收Arc<dyn Subscriber>而非Box<dyn Subscriber>,这样测试起来更顺畅:
修改后的业务代码:
use std::thread; use std::sync::Arc; 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, } } // 改为接收Arc<dyn Subscriber>,方便共享所有权 pub fn start(&mut self, sub: Arc<dyn Subscriber>) { let sub_clone = sub.clone(); self.thread_handler = Some(thread::spawn(move || { sub_clone.callback(); })); } }
对应的测试代码:
#[cfg(test)] mod tests { use super::*; use mockall::*; use std::sync::{Arc, Mutex}; mock! { TestSubscriber {} impl Subscriber for TestSubscriber { fn callback(&self); } } #[test] fn test_subscriber() { let mut publisher = Publisher::new(); let mock_subscriber = Arc::new(Mutex::new(MockTestSubscriber::new())); // 提前设置调用期望 mock_subscriber.lock().unwrap().expect_callback().times(1); // 传递Arc给Publisher,主线程仍保留一份引用 publisher.start(mock_subscriber.clone()); // join线程确保执行完成 if let Some(handle) = publisher.thread_handler.take() { handle.join().unwrap(); } } }
额外提示
- 避免用
thread::sleep等待线程执行,直接调用join()是更可靠的方式,能确保线程执行完毕再结束测试,不会出现期望还没触发测试就结束的情况。 Arc负责共享所有权,Mutex负责提供线程安全的可变访问,这是Rust中处理多线程共享可变状态的标准模式。
备注:内容来源于stack exchange,提问作者Mihai
相关产品推荐
相关产品推荐

