我正在尝试构建一个可用的工厂列表,以在Rust中构建不同类型的处理程序。
pub trait MyHandler {
fn handle();
}
pub trait MyFactory {
fn build() -> Result<Box<dyn MyHandler>, dyn Error>;
}
我有几个具体的处理程序(Foo,和类似的Bar和Baz…):
pub struct MyFoo;
impl MyHandler for MyFoo {
fn handle() {
println!("Foo handler");
}
}
impl MyFactory for MyFoo {
fn build() -> Result<Box<dyn MyHandler>, dyn Error> {
// Do something that may fail
Ok(MyFoo {})
}
}
现在,我想根据名称构建一个可用处理程序的"全局表"。
到目前为止,我试了:
const NOTIFIERS: HashMap<(&str, dyn Fn() -> Result<Box<dyn MyHandler>, Box<dyn Error>)>;
不起作用,因为我必须将数据.insert()
,但const不能修改。const NOTIFIERS: Vec<(&str, Box<dyn Fn() -> Result<Box<dyn MyHandler>, Box<dyn Error>>>)> = vec![ ("foo", Box::new(MyFoo::build)), ("bar", Box::new(MyBar::build)), ("baz", Box::new(MyBaz::build))];
不能编译,因为不允许在常量中分配
我针对这种结构,所以我可以:
- 在添加/删除处理程序时需要更新单一的事实来源
- 有一个名称列表,我可以很容易地迭代和提供在API或CLI
- 有一个简单的全局处理程序工厂,在那里我可以查找处理程序名称并调用构建
除了以上两次尝试之外,
我虽然使用一个枚举,其中每种类型都是
(&str, closure)
的元组,我可以使用strum
crate进行迭代,但它会迭代枚举变体(类型),我仍然需要从其他地方设置枚举值(名称和闭包)…我想我可能会使用宏来自动填充处理程序列表,通过"简单地"添加一个
#[derive(RegisterHandler)
到每种类型的处理程序,但我对Rust太陌生了,无法理解关于宏的任何事情,即使它是在构建时生成的,我仍然会有相同的"问题"关于"存储"结果结构作为特定类型的const。
现在,我手动硬编码处理程序名称两次:一次提供处理程序类型列表,第二次匹配请求的处理程序名称以调用其工厂。它可以工作,但看起来不优雅。
我可以冠名建造全球工厂吗?以及如何"正确地"做到这一点。
?这绝对是可能的,你只需要做一些调整。
使用static,而不是const。
const
变量在使用站点复制。这通常不是你想要的。相反,您可以使用static
变量——它在程序中只有一个实例。static
变量仍然需要const初始化式。
保持简单。
你不需要MyFactory
trait, Rust理解函数指针很好:
type Factory = fn() -> Result<Box<dyn MyHandler>, Box<dyn Error>>;
小提示:您需要框Error
把它们放在一起。
游乐场链接的完整示例:
use std::error::Error;
type Factory = fn() -> Result<Box<dyn MyHandler>, Box<dyn Error>>;
static FACTORIES: &[(&str, Factory)] = &[
("foo", create_foo),
("bar", create_bar),
("baz", create_baz),
];
pub trait MyHandler {
fn handle(&self);
}
fn create_foo() -> Result<Box<dyn MyHandler>, Box<dyn Error>> { todo!() }
fn create_bar() -> Result<Box<dyn MyHandler>, Box<dyn Error>> { todo!() }
fn create_baz() -> Result<Box<dyn MyHandler>, Box<dyn Error>> { todo!() }
fn main() -> Result<(), Box<dyn Error>> {
let factory = FACTORIES
.iter()
.filter_map(|t| (t.0 == "foo").then_some(t.1))
.next()
.unwrap();
factory()?;
Ok(())
}
注意:这是一个概念证明,在生产中,我不建议公开暴露FACTORIES
。相反,我会将其设置为模块私有,并公开一个函数来进行查找,并在失败时提供一个很好的错误消息。