如何定义包含可以在运行时定义实现的通用特征的注册表

How to define a registry which contains generic trait that implementations may be defined at runtime

我在设计具有通用类型的注册表时遇到问题 (playground):

use std::fs::File;
use std::collections::HashMap;

type Result<T> = std::result::Result<T, std::io::Error>;

// extern crate that enforce an associate type T 
trait Reader {
    type T;
    fn get_reader(&self) -> Result<Self::T>;
}

// my lib code below
trait ProtocolHandler<R> {
    fn get_reader(&self, path: &str) -> Result<Box<dyn Reader<T = R>>>;
}

struct LocalFSHandler {}

// Each Handler has fixed type for R, here File for LocalFSHandler
impl ProtocolHandler<File> for LocalFSHandler {
    fn get_reader(&self, path: &str) -> Result<Box<dyn Reader<T = File>>> {
        todo!()
    }
}

struct HandlerRegistry {
    // this doesn't compile since lacks of generic argument R
    handlers: HashMap<String, Box<dyn ProtocolHandler>>
}

impl HandlerRegistry {
    fn new() -> Self {
        let mut map: HashMap<String, Box<dyn ProtocolHandler>> = HashMap::new();
        map.insert("local", Box::new(LocalFSHandler {}));
        Self {
            handlers: map,
        }
    }
    
    fn get(&self, name: &str) -> Option<Box<dyn ProtocolHandler>> {
        self.handlers.get(name).cloned()
    }
}
// end of my lib code

// user code
fn main() {
    let registry = HandlerRegistry::new();
    // register one's own handler to registry, which is not known to my lib
    // therefore I cannot try cast by defining a Box<dyn Any> for hashmap value?
    
    // how can I made my lib handler implementation agnostic and cast to the
    // right one at runtime while getting out from the HashMap?
}

我应该如何调整我的代码才能使我的注册表本身实现不可知并可能在运行时由用户填充?

我认为我无法将 hashMap 值定义为 Box<dyn Any>,因为我无法转换为我的库未知的每种可能的处理程序类型?

您不能在同一个 HashMap 中包含不同特征的特征对象 – 如果这些不同特征只是同一通用基本特征的不同变体,那也是如此。

在你的情况下,你可以持有 std::any::Any 并确保这些对象是 ProtocolHandler<T> 的某种变体,同时处理 insert()get() (请注意我添加了一些内联评论):

use std::collections::HashMap;
use std::fs::File;

type Result<T> = std::result::Result<T, std::io::Error>;

trait Reader {
    type T;
    fn get_reader(&self) -> Result<Self::T>;
}

trait ProtocolHandler<R> {
    fn get_reader(&self, path: &str) -> Result<Box<dyn Reader<T = R>>>;
}

struct LocalFSHandler {}

impl ProtocolHandler<File> for LocalFSHandler {
    fn get_reader(&self, path: &str) -> Result<Box<dyn Reader<T = File>>> {
        todo!()
    }
}

struct SomeOtherHandler {}

impl ProtocolHandler<()> for SomeOtherHandler {
    fn get_reader(&self, path: &str) -> Result<Box<dyn Reader<T = ()>>> {
        todo!()
    }
}

struct HandlerRegistry {
    /* We can't only allow ProtocolHandler<R> for any R here. We could introduce
       a new trait that is held here and has a `impl<R> NewTrait for
       ProtocolHandler<R>`, but it is easier to just have Any in the HashMap and
       to check types vi bounds on our methods. 
    */
    handlers: HashMap<String, Box<dyn std::any::Any>>,
}

impl HandlerRegistry {
    fn new() -> Self {
        let map: HashMap<String, Box<dyn std::any::Any>> = HashMap::new();
        // switched the order of the next two statements to show HandlerRegistry::insert() in action
        let mut result = Self { handlers: map };
        result.insert("local", LocalFSHandler {});
        result
    }

    // both insert() and get() are generic for any ProtocolHandler type
    fn insert<T: 'static + ProtocolHandler<R>, R, K: Into<String>>(&mut self, key: K, value: T) {
        self.handlers.insert(key.into(), Box::new(value));
    }

    fn get<T: 'static + ProtocolHandler<R>, R>(&self, key: &str) -> Option<&T> {
        match self
            .handlers
            .get(key)
            .map(|obj| (**obj).downcast_ref::<T>())
        {
            Some(some) => some,
            None => None,
        }
    }
}

fn main() {
    let registry = HandlerRegistry::new();
    // If you now get "local" as a LocalFSHandler, this returns Some(LocalFSHandler)...
    assert!(registry.get::<LocalFSHandler, _>("local").is_some());
    // But if you try to get it as SomeOtherHandler, this returns None, because that's the wrong type
    assert!(registry.get::<SomeOtherHandler, _>("local").is_none());
}

Playground

请注意,您或多或少会在此处选择退出严格键入。您将无法在编译时注意到键入问题,只能在运行时注意到。我不知道这将在什么情况下使用——如果这真的是你想要和需要的,那就去做吧。否则,我建议重新考虑设计。例如,如果您知道可能存在 ProtocolHandler<R> 的所有 R 类型,最好保存一个包含这些可能的 ProtocolHandler 变体的枚举而不是 Any