You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 08:48:06