Search code examples
genericsrustpolymorphismimplicit-conversion

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


I have two traits:

trait Foo {}
trait Bar {}

struct FooImpl;
impl Foo for FooImpl {}

struct BarImpl;
impl Bar for BarImpl {}

And a third type I want to convert into:

struct Baz;

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

I can't define two impls of IntoBaz for the two traits because of coherence, so I wrap one instead:

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

And I don't wrap the other:

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

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

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

So far so good, but why doesn't this line work?

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

Motivation

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

The easiest way would be to define some internal Write trait which covers the shared behaviour, but the coherence problem means I can't just write From<io::Write> instances to the internal trait.

I've tried wrapping io::Write instances, to make the coercion explicit so the compiler prioritises the shorter path and avoids the incoherence, but it won't auto-coerce using the From instance.


Solution

  • Look at the error message:

    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) {}
       | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    It's saying that FooImpl doesn't have an implementation of Bar, which is a requirement of your blanket IntoBaz for B implementation.

    The FooWrapper implementation is not relevant because FooImpl is not the same as FooWrapper. The From and Into traits provide a way to convert between types, but it doesn't happen automatically.

    You might try adding an implementation for things that can be converted into FooWrapper, but this won't work because the implementations could overlap (and specialization is not stable yet).

    But you can define an IntoBaz implementation for just FooImpl:

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

    Which will make your code compile:

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