I have a container type C<T>
that holds an object of type T
.
struct C<T> {
insides: T
}
Let's say I have two types A
and B
which can be converted between using the From
trait:
struct A { }
struct B { }
impl From<A> for B {
fn from(a: A) -> B {
B { }
}
}
In my case it is natural that I should be able to easily put C<A>
wherever C<B>
is needed using this conversion. This can be done by extending From
to the container type:
impl From<C<A>> for C<B> {
fn from(ca: C<A>) -> C<B> {
C { insides: B::from(ca.insides) }
}
}
fn foo(cb: C<B>) {}
fn main() {
let ca = C { insides: A {} };
foo(ca.into());
}
So far so good, this all compiles and works as expected.
But this is a rather natural pattern that is applicable to any pair of convertible types, not only to A
and B
. So one might want a blanket trait implementation:
impl<T, U> From<C<T>> for C<U> where U: From<T> {
fn from(t: C<T>) -> C<U> {
C { insides: U::from(t.insides) }
}
}
This doesn't work:
error[E0119]: conflicting implementations of trait `std::convert::From<C<_>>` for type `C<_>`
--> src/main.rs:19:1
|
19 | impl<T, U> From<C<T>> for C<U> where U: From<T> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `core`:
- impl<T> From<T> for T;
For more information about this error, try `rustc --explain E0119`.
I understand the error: for any X
if we specify the impl for T=X,U=X
we get a conflict, because X: From<X>
for all X
. But this is a rather annoying edge case. Is there a way to work around this? Somehow specify a bound saying T != U
, or any other way of telling Rust to use whatever blanket implementation there is in core
for T=U
and my impl for the rest?
On nightly, you can do that with auto traits:
#![feature(auto_traits, negative_impls)]
auto trait NotSame {}
impl<T> !NotSame for (T, T) {}
struct C<T> {
insides: T,
}
impl<T, U> From<C<T>> for C<U>
where
U: From<T>,
(T, U): NotSame,
{
fn from(t: C<T>) -> C<U> {
C {
insides: U::from(t.insides),
}
}
}
Playground
Note, however, that it may have the same soundness hole of specialization (when interacting with lifetimes) - I'm not sure about that.
Note, however, that this is actually (ab)using a compiler bug - the compiler doesn't analyze and prove that the impls do not overlap, but instead these features are not fully implemented and thus the compiler doesn't check for coherence. This means that you need more type annotations, and also means that you cannot use the impl<T> From<C<T>> for C<T> {}
in std since it overlaps with yours:
#![feature(auto_traits, negative_impls)]
auto trait NotSame {}
// Need at least one negative impl
impl !NotSame for (i32, String) {}
#[derive(Debug)]
struct C<T> {
insides: T,
}
impl<T, U> From<C<T>> for C<U>
where
U: From<T>,
(T, U): NotSame,
{
fn from(t: C<T>) -> C<U> {
C {
insides: U::from(t.insides),
}
}
}
#[derive(Debug)]
struct A;
#[derive(Debug)]
struct B;
impl From<A> for B {
fn from(_: A) -> B { B }
}
fn main() {
let c: C<B> = C { insides: B };
dbg!(<C<B> as From<C<B>>>::from(c));
}
Playground
Don't use that in production, especially since it may be fixed at any time.
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