【问题标题】:How can I implement `From<some trait's associated type>?`如何实现`From<some trait's associated type>?`
【发布时间】:2020-12-16 17:56:52
【问题描述】:

我正在编写一个新的 crate,我希望它可以用于任何 trait 的实现(在另一个 crate 中定义)。特征看起来像这样:

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&lt;T&gt; core::convert::From&lt;T&gt; for T 冲突。我想我明白为什么 - Trait 的其他一些实现者可以设置 type Error = my_crate::Error 使得 impls 都适用 - 但我还能如何实现类似的语义?

我查看了其他一些 crate,他们似乎通过将 Error (或等效的)泛型而不是错误类型本身而不是 trait 实现来处理这个问题。当然可以,但是:

  • 直到我们有inherent associated types,它更加冗长。我的 T 实际上实现了多个特征,每个特征都有自己的 Error 类型,所以我现在必须返回 Result&lt;..., Error&lt;&lt;T as TraitA&gt;::Error, &lt;T as TraitB&gt;::Error&gt;&gt; 等类型;
  • 可以说它的表现力较差(因为与 Trait 的关系丢失了)。

让我的Error 泛化单个类型是当今最好的(最惯用的)选项吗?

【问题讨论】:

  • 您可以使用.map_err(Error::TraitError)? 而不是简单的?,而不是实现From,这样错误类型已经是Error 而不是T::Error? 将使用用T = Error 覆盖impl&lt;T&gt; From&lt;T&gt; for T
  • 是的,这行得通,并且考虑它是它可以工作的唯一方式。 From trait 只是作用于一个类型,它不知道它来自哪里。如果TraitA::ErrorTraitB::Error 具有相同的类型会怎样?我仍然必须将Error 设为通用,但它可以只针对一种类型通用,而不是每个单独的特征。
  • 您不能将TT: TraitA + TraitB 绑定,并在Error 中有两个变体,一个TraitA(&lt;T as TraitA&gt;::Error),另一个为TraitB(&lt;T as TraitB&gt;::Error).map_err(...)?,具体取决于使用哪个特征?即使它们具有相同的类型,它们也会有不同的变体,如果使用 .map_err(...)?,则不需要为 Error 实现 From
  • 是的,这正是我所拥有的,因为尝试了您的 map_err() 建议。到目前为止我发现的唯一问题是它仍然很冗长(与? 相比),但公共 API 非常简洁。现在我想知道为什么我没有看到它在其他地方使用过。
  • 需要详细说明要返回的变体,我相信这是实现From 不可行的标准做法,因为它是map_err 的设计目的。如果这解决了您的问题,我将发布一个总结解决方案的答案。

标签: rust


【解决方案1】:

不要为您的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(())
}

On the playground

这里重要的一点是Error 没有From 实现(除了一揽子实现),并且变体是使用map_err 指定的。当Error::DoStuff 传递给map_err 时,这可以解释为fn(&lt;T as TraitA&gt;::Error) -&gt; ErrorError::DoOtherStuff 也是如此。

无论Error 有多少变体,无论它们是否是同一类型,这种方法都是可扩展的。阅读函数的人也可能更清楚,因为他们可以找出某个错误来自某个地方,而无需检查 From 实现以及被转换的类型出现在函数中的位置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-30
    • 1970-01-01
    • 2019-09-03
    • 2013-05-07
    • 2023-02-08
    • 2021-05-19
    • 2019-11-16
    相关资源
    最近更新 更多