如何实现 `From<某个trait的关联类型>`?

4

我正在编写一个新的crate,希望它可以与另一个crate中定义的任何trait实现一起使用。该trait大致如下:

pub trait Trait {
   type Error;
   ...
}

我有自己的Error类型,但有时我只想原样转发底层错误。我的直觉是定义一个如下的类型:

pub enum Error<T: Trait> {
    TraitError(T::Error),
    ...
}

这类似于 thiserror 推荐的模式,看起来很通用。它可以正常工作,但我也想在我的实现中使用 ?,所以我需要实现 From
impl<T: Trait> From<T::Error> for Error<T> {
    fn from(e: T::Error) -> Self { Self::TraitError(e) }
}

那样做会失败,因为它与impl<T> core::convert::From<T> for T相冲突。我想我明白原因了——其他实现Trait的人可能会设置type Error = my_crate::Error,以使两个impl都适用——但我该如何实现类似的语义呢?
我查看了一些其他的crate,它们似乎通过使它们的Error(或等效物)针对错误类型本身而不是trait实现来处理这个问题。当然,这可以解决问题,但是:
  • 在我们拥有固有关联类型之前,这会更加冗长。我的T实际上实现了多个trait,每个trait都有自己的Error类型,因此现在我必须返回像Result<..., Error<<T as TraitA>::Error, <T as TraitB>::Error>>这样的类型;
  • 这可能表达能力较差(因为与Trait的关系丢失了)。

如今,对于我的Error而言,是否将其泛化为各种类型是最佳(最符合惯用法)的选项?


1
不必实现 From,你可以使用 .map_err(Error::TraitError)? 代替简单的 ?,这样错误类型就是 Error 而不是 T::Error,并且 ? 将使用通用的 impl<T> From<T> for T,其中 T = Error - Filipe Rodrigues
是的,这样做是可行的,而且思考它是唯一能够实现的方式。From 特质只对类型进行操作,它不知道它来自哪里。如果 TraitA::ErrorTraitB::Error 有相同的类型会发生什么?我仍然需要使 Error 成为泛型,但它可以针对一个类型进行泛型化,而不是每个单独的特质。 - jbramley
你不能将TT: TraitA + TraitB绑定,并在Error中有两个变体,一个是TraitA(<T as TraitA>::Error),另一个是TraitB(<T as TraitB>::Error),并根据使用的特征使用.map_err(...)?吗?即使它们具有相同的类型,它们也会处于不同的变体中,如果使用.map_err(...)?,则无需为Error实现From - Filipe Rodrigues
是的,这正是我从您的 map_err() 建议尝试以来的情况。到目前为止,我发现唯一的问题是它仍然冗长(与 ? 相比),但公共 API 非常简洁。现在我想知道为什么我没有看到其他地方使用它。 - jbramley
需要冗长的语法来指定你想要返回哪个变量,我认为这是实现From不可行时的标准做法,因为这就是map_err的设计初衷。如果这解决了你的问题,我会发布一个总结解决方案的答案。 - Filipe Rodrigues
请帮忙,谢谢! - jbramley
1个回答

2

不要为您的Error枚举实现From,相反,考虑使用Result::map_err?结合使用来指定要返回的变体。

对于使用关联类型的类型参数泛型枚举也适用,如下所示:

trait TraitA {
  type Error;
  fn do_stuff(&self) -> Result<(), Self::Error>;
}

trait TraitB {
  type Error;
  fn do_other_stuff(&self) -> Result<(), Self::Error>;
}

enum Error<T: TraitA + TraitB> {
  DoStuff(<T as TraitA>::Error),
  DoOtherStuff(<T as TraitB>::Error),
}

fn my_function<T: TraitA + TraitB>(t: T) -> Result<(), Error<T>> {
  t.do_stuff().map_err(Error::DoStuff)?;
  t.do_other_stuff().map_err(Error::DoOtherStuff)?;
  Ok(())
}

在游乐场上

这里需要注意的是,Error 没有 From 实现(除了通用实现),并且使用 map_err 来指定变量。当传递给 map_err 时,Error::DoStuff 可以被解释为 fn(<T as TraitA>::Error) -> Error。对于 Error::DoOtherStuff 同样如此。

该方法可扩展至任意数量的 Error 变量,并且无论它们是否为相同类型都可以使用。对于读取函数的人来说,这种方法可能更清晰,因为他们不需要检查 From 实现和要转换的类型在函数中出现的位置,就能判断某个错误来自哪个地方。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接