Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blanket `From` implementation for a container type that converts the contained type

Tags:

generics

rust

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?

like image 613
V0ldek Avatar asked Nov 24 '21 21:11

V0ldek


1 Answers

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.

like image 140
Chayim Friedman Avatar answered Oct 08 '22 02:10

Chayim Friedman