为什么“From”不自动用于强制转换为特征实现类型

Why isn't `From` automatically used for coercing to trait implementing type

我有两个特点:

trait Foo {}
trait Bar {}

struct FooImpl;
impl Foo for FooImpl {}

struct BarImpl;
impl Bar for BarImpl {}

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

struct Baz;

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

由于连贯性,我无法为这两个特征定义两个 implIntoBaz,所以我换一个:

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);
}

动机

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

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

我已经尝试包装 io::Write 个实例,使强制转换显式化,这样编译器会优先考虑较短的路径并避免不连贯,但它不会使用 From 实例自动强制转换.

查看报错信息:

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 特征提供了一种在类型之间进行转换的方法,但它不会自动发生。

您可以尝试为可以转换为 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);
}

所提问题完全正确。 FromInto 在类型级别上没有任何特殊意义。

但是,您可能只是问得太狭隘了。看起来您希望 do_thing(&BarImpl)do_thing(&FooImpl) 编译并执行 "right" 操作。如果这就是您所需要的,那么有一个有点棘手的替代方法可以工作:向 IntoBaz 添加一个类型参数并使用不同的类型使 impl 不重叠。

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);
}

I'm trying to add io::Write support to a library with fmt::Write support without introducing a breaking change.

不幸的是,这个建议在技术上是一个重大变化。如果有某种类型同时实现了 io::Writefmt::Write,那么 do_thing(&implements_both)(过去使用 fmt::Write)现在将失败由于歧义而编译。但是任何 trait 选择明确的地方仍然会像以前一样编译,所以破损的风险要低得多。

另请参阅:

  • Can I avoid eager ambiguity resolution for trait implementations with generics?