Isaac van Bakel
Isaac van Bakel

Reputation: 1852

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.

Upvotes: 4

Views: 194

Answers (2)

trent
trent

Reputation: 27895

PeterHall's answer is completely correct about the question as asked. From and Into don't mean anything special on the type level.

However, you might just have asked the question too narrowly. It looks like you want do_thing(&BarImpl) and do_thing(&FooImpl) to compile and do the "right" things. If that's all you need, there is a somewhat tricky alternative approach that could work: add a type parameter to IntoBaz and use different types to make the impls non-overlapping.

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 can now be generic over T:

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

When you call it, if there is only one T that works, the compiler will find it automatically.

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.

Unfortunately this suggestion is technically a breaking change. If there is some type that implements both io::Write and fmt::Write, then do_thing(&implements_both) (which used to use fmt::Write) will now fail to compile due to ambiguity. But any place where the choice of trait is unambiguous will still compile as it did before, so the risk of breakage is much lower.

See also:

Upvotes: 3

Peter Hall
Peter Hall

Reputation: 58725

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

Upvotes: 6

Related Questions