作为我的铁 rust 学习过程的一部分,我正在try 转换我已经使用过几次的图案.这是通过密钥(通常是接口名称)注册的"服务"的存储库.然后,可以查询该存储库以检索随后可以使用的"服务".我过go 曾将其用作简单的DI方法,以允许在测试期间将实现替换为模拟.

我在Rust中实现这一简单版本时遇到了一些问题.

我希望看到的是这样的代码:

// The service contract
trait DoSomethingService {
    fn do_something(&self);
}

// The service implementation
struct DoSomethingServiceImpl {}

impl DoSomethingService for DoSomethingServiceImpl {
    fn do_something(&self) {
        println!("Doing something for MyService");
    }
}

// BaseService is a marker trait
impl BaseService for DoSomethingServiceImpl {}


fn main() {
    let mut repo = ServiceRepo::new();

    let service1 = DoSomethingServiceImpl {};

    // Register our implementation with the repo as the trait DoSomethingService
    repo.register_service::<DoSomethingService>(Box::new(service1));

    // Get the service from the repo and have it cast to the trait NOT the concrete type
    if let Some(x) = repo.get_service::<DoSomethingService>() {
        x.do_something();
    }
}

我当前的实现如下所示

#[derive(Default)]
struct ServiceRepo {
    services: HashMap<String, Box<dyn BaseService>>,
}

impl ServiceRepo {
    fn new() -> Self {
        Self {
            ..Default::default()
        }
    }

    fn register_service<T: BaseService>(&mut self, service: Box<dyn BaseService>) {
        self.services
            .insert(std::any::type_name::<T>().to_string(), service);
    }

    fn get_service<T: BaseService>(&self) -> Option<&Box<T>> {
        let service = self.services.get(&std::any::type_name::<T>().to_string());

        // TODO: Want to return an Option<&T> here
        // Not sure what to do!
    }
}

我"认为"我很好地存储了服务实现,但将其预定义为所需的特征却在回避我.

所以有几个问题:

  1. 这是《铁 rust 》中的一个合理模式吗?如果不是,原因何在?
  2. 我将如何实现这一点?我现在错过了什么?

推荐答案

你不能用你描述的方式来做这件事.原因是,这没有任何意义.特征"描述"类型(他们的能力),而不是反过来.然而,你已经接近了,你可以逆转你的解决方案,让系统正常工作.

您必须将types(不是特征)作为您的服务,并且您可以使用dyn Any来存储它.例如:

use std::any::Any;

struct Service1;

impl Service1 {
    fn do_thing(&self) {
        println!("Service1")
    }
}

struct Service2;

impl Service2 {
    fn do_other_thing(&mut self) {
        println!("Service2")
    }
}

struct Services {
    services: Vec<Box<dyn Any>>,
}

impl Services {
    fn new() -> Self {
        Self { services: vec![] }
    }

    fn register(&mut self, service: Box<dyn Any>) {
        self.services.push(service)
    }

    fn get_service<S: 'static>(&self) -> Option<&S> {
        self.services
            .iter()
            .map(|s| s.downcast_ref::<S>())
            .find(Option::is_some)
            .flatten()
    }

    fn get_service_mut<S: 'static>(&mut self) -> Option<&mut S> {
        self.services
            .iter_mut()
            .map(|s| s.downcast_mut::<S>())
            .find(Option::is_some)
            .flatten()
    }
}

fn main() {
    let mut services = Services::new();
    services.register(Box::new(Service1));

    services.get_service::<Service1>().unwrap().do_thing();
    assert!(services.get_service::<Service2>().is_none());

    services.register(Box::new(Service2));

    services.get_service::<Service1>().unwrap().do_thing();
    services
        .get_service_mut::<Service2>()
        .unwrap()
        .do_other_thing();
}

编辑.顺便说一句,do not使用std::any::type_name的输出作为唯一标识服务的基础.这是用于调试目的的only!使用std::any::TypeId::of获得的std::any::TypeId.

Rust相关问答推荐

什么是Rust惯用的方式来使特征向量具有单个向量项的别名?

常量泛型和类型枚举箱有重叠的用途吗?

如何仅使用http机箱发送http请求?

rust 蚀生命周期 行为

铁 rust 中的共享对象实现特征

这是不是在不造成嵌套的情况下从枚举中取出想要的变体的惯用方法?

不能在一个代码分支中具有不变的自身borrow ,而在另一个代码分支中具有可变的self borrow

如何将单个 struct 实例与插入器一起传递到Rust中的映射

在0..1之间将U64转换为F64

为什么`AlternateScreen`在读取输入键时需要按Enter键?

try 创建随机数以常量

使用Rust WASM读取文件

使用 select 处理 SIGINT 和子等待!无阻塞

如何执行数组文字的编译时串联?

如何从宏调用闭包?

可选包装枚举的反序列化

返回迭代器考虑静态生命周期类型

Rust 中的内存管理

Rust typestate 模式:实现多个状态?

如何将 Rust 字符串转换为 i8(c_char) 数组?