rust 创建Rc〈RefCell〈Box〈dyn SomeTrait>>>的两个副本,但其中一个是具体示例

ffx8fchx  于 2023-04-21  发布在  其他
关注(0)|答案(1)|浏览(154)

我试图模拟serialport::new()提供的串口对象,并花了很长时间。Rc<RefCell<dyn SomeTrait>>模式是我一直在使用的一种模式,现在用于任何单例依赖项-比如串口-我已经习惯了它。它可以工作,它让我使用mockall编写高度集中的单元测试,其中所有依赖项都被模拟出来。
但是,今天我遇到了serialport::new(),它返回一个Box<dyn SerialPort>,我想不出一种方法把它转换成Rc<RefCell<dyn SerialPort>>。所以我 Package 了整个盒子,最后得到了Rc<RefCell<Box<dyn SerialPort>>>。但是现在如果不把TestContext中的mock对象定义为另一个Rc<RefCell<Box<dyn SerialPort>>>,我就无法编译我的单元测试。这将阻止我实际调用特殊的mock相关方法,如.expect_send()
这里我需要一些帮助。下面是以最简单的形式重新创建的用例,以及在构建测试时遇到的编译器错误。

use std::cell::RefCell;
use std::rc::Rc;

/// This is not my code. This is a simplified version of the serialport crate.
mod serialport {
    pub trait SerialPort {
        fn send(&self);
    }

    pub struct SomeConcreteSerialPort {}

    impl SerialPort for SomeConcreteSerialPort {
        fn send(&self) {}
    }

    pub fn new() -> Box<dyn SerialPort> {
        Box::new(SomeConcreteSerialPort {})
    }
}

struct MyStruct {
    port: Rc<RefCell<Box<dyn serialport::SerialPort>>>,
}

impl MyStruct {
    fn new() -> Self {
        Self {
            port: Rc::new(RefCell::new(serialport::new())),
        }
    }

    fn do_send(&self) {
        self.port.borrow().send();
    }
}

fn main() {
    let my_struct = MyStruct::new();
    my_struct.do_send();
    println!("The foo is done!");
}

#[cfg(test)]
mod test {
    use mockall::mock;
    use std::cell::RefCell;
    use std::rc::Rc;

    use crate::serialport;
    use crate::MyStruct;

    mock! {
        SerialPort {}

        impl serialport::SerialPort for SerialPort {
            fn send(&self);
        }
    }

    struct TestContext {
        mock_port: Rc<RefCell<Box<MockSerialPort>>>,
        testable: MyStruct,
    }

    impl TestContext {
        fn new() -> Self {
            let mock_port = Rc::new(RefCell::new(Box::new(MockSerialPort::new())));
            Self {
                mock_port: Rc::clone(&mock_port),
                testable: MyStruct { port: mock_port },
            }
        }
    }

    #[test]
    fn test_happy_path() {
        let context = TestContext::new();

        context.mock_port.borrow().expect_send().once();

        context.testable.do_send();
    }
}
error[E0308]: mismatched types
  --> src/main.rs:71:44
   |
71 |                 testable: MyStruct { port: mock_port },
   |                                            ^^^^^^^^^ expected trait object `dyn SerialPort`, found struct `MockSerialPort`
   |
   = note: expected struct `Rc<RefCell<Box<(dyn SerialPort + 'static)>>>`
              found struct `Rc<RefCell<Box<MockSerialPort>>>`

我还将代码发布到GitHub repo,如果你想在本地复制它:https://github.com/DavidZemon/MockingProblems/

niknxzdl

niknxzdl1#

我们可以完全忽略RefCell,并考虑当T实现Trait时将Rc<Box<T>>转换为Rc<Box<dyn Trait>>的问题。
为了做到这一点,这必须不改变Rc内部值的内存表示,因此Box<T>Box<dyn Trait>必须具有相同的内存表示。然而,Box<T>是一个瘦指针,这意味着它只包含堆上值的地址,而Box<dyn Trait>是一个胖指针。它包含值的地址和T的vtable的地址,T包含运行时的类型信息(如方法)。
所以Box<dyn Trait>大于Box<T>,因此具有不同的存储器布局,所以不可能把Rc<Box<T>>变成Rc<Box<dyn Trait>>(或者以任何方式产生两个Rc,这些类型指向相同的值)。(或任何其他指针类型)是使这不可能的原因。

相关问题