一个棘手的 Rust 泛型问题,我正在抓耳挠腮

A tricky Rust generics problem I'm scratching my head over

我有一个扩展了 3 个 crate 的应用程序:一个包含抽象框架的 crate,另一个包含被选为 cargo feature 的多个插件之一,第三个包含具体实现。

问题是插件决定了整个应用程序的“版本”类型,而实现决定了整个应用程序的错误类型。为了使应用程序可插入多个插件和多个实现,我需要插件中的错误类型是通用的,但我不知道该怎么做。

在下面的最小代码中,我对插件 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));
    }
}

最后,我选择了 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");
        }
    }
}

解决这个问题的另一种方法是提供一种在插件上映射错误类型的方法。

我会通过将 MappedPlugin 类型添加到 traits 板条箱来做到这一点

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)

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

可以看到完整的工作版本here