【问题标题】:Why isn't `From` automatically used for coercing to trait implementing type为什么`From`不自动用于强制特征实现类型
【发布时间】:2018-10-07 03:14:36
【问题描述】:

我有两个特点:

trait Foo {}
trait Bar {}

struct FooImpl;
impl Foo for FooImpl {}

struct BarImpl;
impl Bar for BarImpl {}

还有我想转换成的第三种类型:

struct Baz;

trait IntoBaz {
    fn into(self) -> Baz;
}

由于连贯性,我无法为这两个特征定义两个 impls 和 IntoBaz,因此我将其包装为一个:

struct FooWrapper<F>(F)
where
    F: Sized;

impl<F: Foo + Sized> From<F> for FooWrapper<F> {
    fn from(f: F) -> FooWrapper<F> {
        FooWrapper(f)
    }
}

impl<F: Foo + Sized> IntoBaz for FooWrapper<F> {
    fn into(self) -> Baz {
        Baz
    }
}

而且我不包装另一个:

impl<B: Bar> IntoBaz for B {
    fn into(self) -> Baz {
        Baz
    }
}

fn do_thing<B: IntoBaz>(b: &B) {}

fn main() {
    do_thing(&BarImpl);
}

到目前为止一切顺利,但为什么这条线不起作用?

fn main() {
    do_thing(&FooImpl);
}

动机

我正在尝试将io::Write 支持添加到具有fmt::Write 支持的库中,而不会引入重大更改。

最简单的方法是定义一些涵盖共享行为的内部 Write 特征,但一致性问题意味着我不能只将 From&lt;io::Write&gt; 实例写入内部特征。

我尝试包装 io::Write 实例,以使强制强制显式化,以便编译器优先考虑较短的路径并避免不连贯,但它不会使用 From 实例自动强制。

【问题讨论】:

  • 错误表明FooImpl 没有实现Bar(这是IntoBaz 的全面实现所必需的)。你不打算这样做吗:do_thing(&amp;FooWrapper(FooImpl));
  • 不,因为目标是让自动强制执行此操作 - 我不明白为什么,在这种情况下,它没有。
  • from()into() 不要只是被自​​动调用。您需要执行以下操作:do_thing(&amp;FooWrapper::from(FooImpl));
  • 获得用户定义的自动强制的唯一合理方法是在某些包装类型上实现Deref,这将导致包装自动衰减为被包装类型。你可以看到full list of coercions in th Nomicon
  • "from() 有时会被语言自动调用" — 它可能似乎适用于某些库,但这只是那些要求参数为 @987654344 的库@ 并显式调用 .into(),就像 do_thing 的主体必须这样做一样。

标签: generics rust polymorphism implicit-conversion


【解决方案1】:

查看错误信息:

error[E0277]: the trait bound `FooImpl: Bar` is not satisfied
  --> src/main.rs:48:5
   |
48 |     do_thing(&FooImpl);
   |     ^^^^^^^^ the trait `Bar` is not implemented for `FooImpl`
   |
   = note: required because of the requirements on the impl of `IntoBaz` for `FooImpl`
note: required by `do_thing`
  --> src/main.rs:45:1
   |
45 | fn do_thing<B: IntoBaz>(b: &B) {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

这是说FooImpl 没有Bar 的实现,这是您的毯子IntoBaz for B 实现的要求。

FooWrapper 实现不相关,因为FooImplFooWrapper 不同。 FromInto trait 提供了一种在类型之间进行转换的方法,但它不会自动发生。

您可以尝试为可以转换为 FooWrapper 的内容添加一个实现,但这不起作用,因为这些实现可能会重叠(并且specialization 还不稳定)。

但您可以为 FooImpl 定义一个 IntoBaz 实现:

impl IntoBaz for FooImpl {
    fn into(self) -> Baz {
        IntoBaz::into(FooWrapper::from(self))
    }
}

这将使您的代码编译:

fn main() {
    do_thing(&BarImpl);
    do_thing(&FooImpl);
}

【讨论】:

  • 我可以为FooImpl 这样做,但这不能解决我在动机中解释的一般问题,这需要相当于两个毯子-impls。我试图通过让类型检查器选择实例来避免重叠(基本上是通过到目标类型的最短强制距离)。
【解决方案2】:

PeterHall's answer 对所提出的问题完全正确。 FromInto 在类型级别上没有任何特殊含义。

但是,您可能只是提出了过于狭隘的问题。看起来您希望 do_thing(&amp;BarImpl)do_thing(&amp;FooImpl) 编译并做“正确”的事情。如果这就是你所需要的,有一个有点棘手的替代方法可以工作:向IntoBaz 添加一个类型参数并使用不同的类型使impls 不重叠。

trait IntoBaz<T> {
    fn into_baz(self) -> Baz;
}

struct ByFoo;
impl<F: Foo> IntoBaz<ByFoo> for F {
    fn into_baz(self) -> Baz {
        Baz
    }
}

struct ByBar;
impl<B: Bar> IntoBaz<ByBar> for B {
    fn into_baz(self) -> Baz {
        Baz
    }
}

do_thing 现在可以泛型超过T

fn do_thing<T, B: IntoBaz<T>>(_: &B) {}

当你调用它时,如果只有一个T可以工作,编译器会自动找到它。

fn main() {
    do_thing(&BarImpl);
    do_thing(&FooImpl);
}

我正在尝试将io::Write 支持添加到具有fmt::Write 支持的库中,而不会引入重大更改。

不幸的是,这个建议在技术上是一个突破性的变化。如果有某种类型同时实现了io::Writefmt::Write,那么do_thing(&amp;implements_both)(以前使用fmt::Write)现在将由于歧义而无法编译。但是任何特征选择明确的地方仍然会像以前一样编译,因此损坏的风险要低得多。

另见:

【讨论】:

  • 感谢您的有趣建议。我尝试了类似的方法,但使用B: Into&lt;I&gt; 后跟自定义I: IntoBaz,但是类型检查器在类型推断中没有使用孤儿规则,所以很遗憾它不起作用。当然,这只是为了避免写&lt;method_name&gt;_io而感到非常悲痛,所以我可能最终会这样做。
猜你喜欢
  • 1970-01-01
  • 2015-10-08
  • 1970-01-01
  • 2015-09-19
  • 2020-03-31
  • 1970-01-01
  • 1970-01-01
  • 2019-12-08
  • 1970-01-01
相关资源
最近更新 更多