如何定义一个注册表,该注册表包含可以在运行时定义实现的通用特性



我在设计通用类型注册表(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>,因为我不能将其转换为我的lib不知道的每个可能的处理程序类型?

你不能在相同的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());
}

游乐场

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

最新更新