Cannot impl<T, U> From<Type<T>> for Type<U>

I am working on a crate which parameterizes a Control type using a Mode type. The Mode types are just empty enums, however they implement a trait with some mode-specific data that can be used for interconversion

So I would like to be able to implement the following:

impl<M: Mode, N: Mode> From<Control<M>> for Control<N> {
    /* ... */
}

However, I run into the problem that this conflicts with impl<T> From<T> for T in std/core, for the case where M is the same type as N.

Is there any stable way to write this impl without generating like 30 different impls?

I'm working on nightly so knowing the appropriate feature for specialization if available would be good too.

2 Likes

This reminds me of my problems with Generic impl TryFrom impossible due to orphan rules, but not sure if these are really related.

(By the way, I didn't entirely ditch From/TryFrom in the end.)

Uh, this is painful. Current best workaround would be not using From, but declare a new trait and use that. Specialization is a incomplete feature, so I would avoid using it.

2 Likes

This is as closest as I got:

#![feature(negative_impls, with_negative_coherence)]

use std::marker::PhantomData;

struct Marker1;
struct Marker2;
struct Marker3;

trait Mode: {}

impl Mode for Marker1 {}
impl Mode for Marker2 {}
impl Mode for Marker3 {}

struct Control<T> {
    phantom: PhantomData<T>,
}

// Conversion via function is easy, but this isn't `From` implementation yet:
fn convert_control<M, N>(from: Control<M>) -> Control<N> {
    Control {
        phantom: PhantomData,
    }
}

trait DistinctMode<M>: Mode {}
impl<T> !DistinctMode<T> for T {} // negative implementation

// We still need to list all combinations of markers here:
impl DistinctMode<Marker1> for Marker2 {}
impl DistinctMode<Marker1> for Marker3 {}
impl DistinctMode<Marker2> for Marker1 {}
impl DistinctMode<Marker2> for Marker3 {}
impl DistinctMode<Marker3> for Marker1 {}
impl DistinctMode<Marker3> for Marker2 {}

// But it doesn't work anyway :-(
impl<M: Mode, N: DistinctMode<M>> From<Control<M>> for Control<N> {
    fn from(from: Control<M>) -> Self {
        convert_control(from)
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `std::convert::From<Control<_>>` for type `Control<_>`
  --> src/lib.rs:38:1
   |
38 | impl<M: Mode, N: DistinctMode<M>> From<Control<M>> for Control<N> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `core`:
           - impl<T> From<T> for T;

For more information about this error, try `rustc --explain E0119`.
error: could not compile `playground` due to previous error

I don't understand why it doesn't work. Maybe has to do with with_negative_coherence not working well. See also this, where I got warned to use it: Conflicting implementations despite negative_impl’s.

You'll notice that even Option doesn't have an impl like that.

It's a common desire, but there's no great answer right now.

Oh, very interesting! I never thought on that being missing too.

How do we solve it? We can use Option::map:

fn main() {
    let a: Option<i16> = Some(5);
    // let b: Option<i32> = a.into(); // won't work
    let b: Option<i32> = a.map(|inner| inner.into()); // but this works!
    println!("{b:?}");
}

(Playground)

Output:

Some(5)

Maybe if Rust had a functor trait, this conversion could be expressed in a more abstract fashion. But I think that generic associated types are needed that go beyond lifetimes (but support real type mapping). But not sure on that or what's the state on functors in Rust, and whether this could help at all.

I think the From<T> for T implementation was added at a time when specialization seemed to be just around the corner. If all of the soundness holes can be patched, then it should be able to decide between From<T> for T and From<Option<U>> for Option<T>.

As a workaround you could define a macro_rules macro (parameterized on the type arg to From) that expands to exactly such an impl, and then call it with each of the From types. So something like this:

#[allow(non_snake_case)]
macro_rules! impl_From {
    ($from:ty, $to:ty) => {
        impl From<Control<$from>> for Control<$to> {
            fn from(val: Control<$from>) -> Self {
                // TODO: perform the conversion
            } 
        } 
    }
} 

The idea there is that since each of the generated impls won't have any generic arguments, they shouldn't cause any conflicts.

Note that I haven't tried to run the code, nor is the example complete as the actual From fn body is missing.

Well...

fn fmap<T: Try, R>(
    x: T,
    f: impl FnOnce(<T as Try>::Output) -> R,
) -> <<T as Try>::Residual as Residual<R>>::TryType
where
    <T as Try>::Residual: Residual<R>,
{
    Try::from_output(f(x?))
}
fn add_one(x: i32) -> i32 { x + 1 }
assert_eq!(fmap(Some(1), add_one), Some(2));
assert_eq!(fmap(Ok::<_, String>(1), add_one), Ok(2));
assert_eq!(fmap(Continue::<String, _>(1), add_one), Continue(2));

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=920e85183801a187c1f6f95650f6a97c

1 Like

:exploding_head:

I'm trying to understand, but it will take me a while. Certainly this motivates me to look at Try and FromResidual again. The trait Residual is new :face_with_monocle:! (not to be confused with the associated type Residual)

Not sure if this is really a functor though? It is FnOnce there, so it won't work with a Vec or array, for example?


 fn main() {
     fn add_one(x: i32) -> i32 { x + 1 }
     assert_eq!(fmap(Some(1), add_one), Some(2));
     assert_eq!(fmap(Ok::<_, String>(1), add_one), Ok(2));
     assert_eq!(fmap(Continue::<String, _>(1), add_one), Continue(2));
+    assert_eq!(fmap([5,6], add_one), [6,7]);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `[{integer}; 2]: Try` is not satisfied
  --> src/main.rs:19:21
   |
19 |     assert_eq!(fmap([5,6], add_one), [6,7]);
   |                ---- ^^^^^ the trait `Try` is not implemented for `[{integer}; 2]`
   |                |
   |                required by a bound introduced by this call
   |
note: required by a bound in `fmap`
  --> src/main.rs:4:12
   |
4  | fn fmap<T: Try, R>(
   |            ^^^ required by this bound in `fmap`

error[E0277]: the trait bound `[{integer}; 2]: Try` is not satisfied
  --> src/main.rs:19:16
   |
19 |     assert_eq!(fmap([5,6], add_one), [6,7]);
   |                ^^^^ the trait `Try` is not implemented for `[{integer}; 2]`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to 2 previous errors


Try and Try::Residual are explained in RFC #3058. I read it multiple times in past and it still keeps confusing me :sweat_smile:.

It's using the ? mechanisms, so no, it's not really a general thing. It's basically just try { f(x?) }.

Notice the author of that RFC :upside_down_face:

But yeah, there's a plan afoot (see https://github.com/rust-lang/rust/issues/84277#issuecomment-1066120333) to rename some parts and restructure it a bit.

I'm a bit confuse. If I understand the doc correctly, Residual<R> is for retrieving the type that implement Try.
In this case isn't T ?
Why using this complexe type instead ?

There can be several implementations of Residual<O> for a given type, each mapping to a different TryType. Thus Residual can map an (Output) type to a Try type.

But I also find it very confusing :sweat_smile:.

Oh I saw you being author of try_trait_v2_residual, but didn't see you authored the other RFC too.

I included the link to the RFC for the other readers, who might be similarly confused as I am when trying to understand Try and Residual.

Hmm, I like the idea to separate Branch from Try. It does seem to make things easier to understand for me (whether it will or will not remain separated in the end).

Not sure if Branch::Break is a better name than …::Residual. These types like Result<!, io::Error>, for example. Renaming it to Break might cause confusion with ControlFlow::Break(B), where B is a "raw" type rather than a "colored" type as in case of Branch::Break (or Try::Residual).

But maybe Break overall causes less confusion, because it's also difficult to remember which of Output or Residual is short circuiting (Continue and Break is much more clear in that matter).

I'm looking forward to play around with the renamed (or split up) traits/types.

I also would like to understand the problem of "functors" better (in regard to Rust). Any good reading in that matter?

I don't know of any. They probably need generic_associated_types - The Rust Unstable Book before they can work how you want, since you can't implement things on Vec right now, only on Vec<T>.

It's to support Option<T> -> Option<U>, rather than just Option<T> -> Option<T>. Admittedly the examples didn't take advantage of that, so here's a tweaked demo where it needs it: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=cec8e16c0d052093d18181a7b1baeee0

2 Likes

I just wanted to say, "but GAT's are only supporting lifetimes and not type arguments!" But then I looked into RFC #1598 and noticed that you can have type arguments too!?

:flushed:

#![feature(generic_associated_types)]

struct Vectorize;

trait TypeMapper {
    type Mapped<T>;
    fn wrap<T>(value: T) -> Self::Mapped<T>;
}

impl TypeMapper for Vectorize {
    type Mapped<T> = Vec<T>;
    fn wrap<T>(value: T) -> Self::Mapped<T> {
        vec![value]
    }
}

fn wrap<M: TypeMapper, T>(value: T) -> M::Mapped<T> {
    M::wrap(value)
}

fn main() {
    let i = 79;
    let vec = wrap::<Vectorize, i32>(i);
    println!("{vec:?}");
}

(Playground)

Output:

[79]

That opens up so many more possibilities than I thought!! (Maybe I did came across this in past, but if I did, I totally forgot it was possible!)


I just searched the web, and found the following blog post. I think I have read it in past (but was very new to Rust back then):

Monads and GATs in nightly Rust, by Michael Snoyman, December 7, 2020

See also, Associated Type Constructors [1]:

The last two speculate beyond the GAT feature. [2]

Lifetime GATs can be indirectly accomplished on stable because we have for<'a> higher-ranked trait bounds. But type parameter GATs cannot, as we don't have for<T> higher-ranked trait bounds.


  1. aka GATs ↩︎

  2. Including generic ATC parameters, which has come up in some other threads you've been a part of. ↩︎

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.