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 impl
s 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);
}
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.
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);
}
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 impl
s 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 withfmt::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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With