一个棘手的 Rust 泛型问题,我正在挠头



我有一个扩展到3个板条箱的应用程序:一个板条箱包含抽象框架,另一个板条箱内包含许多被选为货物功能的插件之一,第三个板条箱则包含具体实现。

问题是插件确定了";版本";类型,并且实现确定整个应用程序的Errors类型。为了使应用程序能够在多个插件和多个实现之间插入,我需要插件中的Errors类型是通用的,但我不知道如何做到这一点。

在下面的最小代码中,我对插件type Errors = MyThingErrors进行了硬编码,以显示一些有效的东西。但我需要这里的错误类型是通用的,而不是具体的。我尝试过各种通用参数的组合,但无法编译。

那么,有什么诀窍吗?我是不是把Rust仿制药推得太远了?这是一个XY问题的例子吗?也许我应该采取不同的方法?

如有任何建议,不胜感激。

以下是工作示例:

use thiserror::Error;
// ----------------------------------------
// Abstract traits crate
trait Thing {
type Errors;
type Version;
fn plugin(&self) -> &Box<dyn Plugin<Errors = Self::Errors, Version = Self::Version>>;
fn foo(&self) -> Result<(), Self::Errors>;
}
trait Plugin {
type Errors;
type Version;
fn bar(&self) -> Result<(), Self::Errors>;
}
// ----------------------------------------
// plugin crate
#[derive(Error, Debug, PartialEq)]
enum PluginErrors {
#[error("First Plugin error")]
Error1,
}
struct PluginVersion {}
struct MyPlugin {}
impl Plugin for MyPlugin {
type Errors = MyThingErrors;
type Version = PluginVersion;
fn bar(&self) -> Result<(), Self::Errors> {
Err(MyThingErrors::PluginError(PluginErrors::Error1))
}
}
// ----------------------------------------
// concrete implementation crate
#[derive(Error, Debug, PartialEq)]
enum MyThingErrors {
#[error("First MyThing error")]
MTError1,
#[error("Plugin Error: {0}")]
PluginError(#[from] PluginErrors),
}
struct MyThing {
p: Box<dyn Plugin<Errors = MyThingErrors, Version = <MyThing as Thing>::Version>>,
}
impl Thing for MyThing {
type Version = PluginVersion;
type Errors = MyThingErrors;
fn plugin(&self) -> &Box<dyn Plugin<Version = Self::Version, Errors = Self::Errors>> {
&self.p
}
fn foo(&self) -> Result<(), Self::Errors> {
Err(MyThingErrors::MTError1)
}
}
fn main() {
let t = MyThing {
p: Box::new(MyPlugin {}),
};
if let Err(e1) = t.foo() {
assert_eq!(e1, MyThingErrors::MTError1);
}
if let Err(e2) = t.p.bar() {
assert_eq!(e2, MyThingErrors::PluginError(PluginErrors::Error1));
}
}

另一种方法是提供一种在插件上映射错误类型的方法。

我会通过在特征板条箱中添加MappedPlugin类型来实现这一点

struct MappedPlugin<P,F> {
p: P,
f: F,
}
impl<P,F, E> Plugin for MappedPlugin<P,F>
where
P: Plugin,
F: Fn(P::Errors) -> E,
{
type Errors = E;
type Version = P::Version;
fn bar(&self) -> Result<(), Self::Errors> {
self.p.bar().map_err(&self.f)
}
}

然后在主机箱中包装并创建创建创建的插件:

fn main() {
let f = |e:PluginErrors| -> MyThingErrors { MyThingErrors::PluginError(e) };
let t = MyThing {
p: Box::new(MappedPlugin{ p:MyPlugin {}, f:f }),
};
if let Err(e1) = t.foo() {
assert_eq!(e1, MyThingErrors::MTError1);
}
if let Err(e2) = t.p.bar() {
assert_eq!(e2, MyThingErrors::PluginError(PluginErrors::Error1));
}
}

您可以添加一个简单的函数来为您进行包装,使其成为MyPlugin{}.map_err(|e| MyThingErrors::PluginError)

主机箱仍然需要了解插件机箱中的错误类型。

完整的工作版本可以在这里看到。

最后,我选择了Box<dyn Error>版本。代码简单了一点,不必具有Errors关联类型,这不是一件坏事。具有通用错误的要求得到了满足,但这不是一个理想的解决方案,因为实现机箱需要知道错误类型,以便打开和处理错误。

以下是由此产生的最小代码,适用于任何对感兴趣的人

use std::error::Error;
use thiserror::Error;
// ----------------------------------------
// Abstract traits crate
trait Thing {
type Version;
fn plugin(&self) -> &Box<dyn Plugin<Version = Self::Version>>;
fn foo(&self) -> Result<(), Box<dyn Error>>;
}
trait Plugin {
type Version;
fn bar(&self) -> Result<(), Box<dyn Error>>;
}
// ----------------------------------------
// plugin crate
#[derive(Error, Debug, PartialEq)]
enum PluginErrors {
#[error("First Plugin error")]
Error1,
}
struct PluginVersion {}
struct MyPlugin {}
impl Plugin for MyPlugin {
type Version = PluginVersion;
fn bar(&self) -> Result<(), Box<dyn Error>> {
Err(Box::new(PluginErrors::Error1))
}
}
// ----------------------------------------
// concrete implementation crate
#[derive(Error, Debug, PartialEq)]
enum MyThingErrors {
#[error("First MyThing error")]
MTError1,
// #[error("Plugin Error: {0}")]
// PluginError(#[from] PluginErrors),
}
struct MyThing {
p: Box<dyn Plugin<Version = <MyThing as Thing>::Version>>,
}
impl Thing for MyThing {
type Version = PluginVersion;
fn plugin(&self) -> &Box<dyn Plugin<Version = Self::Version>> {
&self.p
}
fn foo(&self) -> Result<(), Box<dyn Error>> {
Err(Box::new(MyThingErrors::MTError1))
}
}
fn main() {
let t = MyThing {
p: Box::new(MyPlugin {}),
};
if let Err(e1) = t.foo() {
if let Some(err) = e1.downcast_ref::<MyThingErrors>() {
assert_eq!(err.to_string(), "First MyThing error");
}
}
if let Err(e2) = t.p.bar() {
if let Some(err) = e2.downcast_ref::<PluginErrors>() {
assert_eq!(err.to_string(), "First Plugin error");
}
}
}

最新更新