What about this impl violates impl rules?

#1
impl<Head, Tail, Other> From<Other> for AB
where
    Other: Into<::frunk::coproduct::Coproduct<Head, Tail>>,
    Self: From<::frunk::coproduct::Coproduct<Head, Tail>>,
{
    fn from(other: Other) -> Self {
        let coprod = other.into();
        Self::from(coprod)
    }
}

Head and Tail are “not constrained by the impl trait, self type, or predicates”. (For what it’s worth, flipping the second where clause to an Into would also work for the types, but doesn’t change the error.)

I guess the problem comes down to that rustc needs to infer what Head and Tail are, but can’t in this context. But what’s the specific rule I’m violating here, and why does it exist? I can’t exactly figure out why this is too generic for rustc to handle. (I don’t think I’m doing anything more mean to the type system than just pulling in frunk in the first place.)

[[ If people wonder where this came from, I tried to make a system for ad-hoc conversions between enums using frunk::Coproduct, similar to how frunk::Generic converts between structs using frunk::HList. Unfortunately the middleman trait seems to be necessary, so it won’t “just work” with From in the ? expansion. ]]

0 Likes

#2

The issue is compiler cannot uniquely infer/deduce what concrete types would be filled in for Head and Tail if it knows Other and/or Self. That’s because Into/From can be implemented for multiple types by the same type. For example, some concrete Other type can impl Into<Coproduct<i32, f64>>, Into<Coproduct<i64, i32>>, and so on (I don’t know frunk enough to know whether those particular types are valid or not, but it doesn’t really matter for the overall point). This type of error/condition usually comes up with constraints using traits with generic parameters, rather than associated types.

2 Likes

#3

@vitalyd explained it, but lets see it in action with a simplified example.

// Dummy type
struct Foo;

// Your impl
impl<A, B> From<A> for Foo
    where A: Into<B>, Self: From<B>
{
    fn from(a: A) -> Foo { Foo } // insides don't matter
}

// My impls for the example
impl From<u8> for Foo {
    fn from(a: A) -> Foo { Foo }
}

impl From<u16> for Foo {
    fn from(a: A) -> Foo { Foo }
}

/*
note that u32: From<u8> and u32: From<u16>
*/
const foo_from: fn(u32) -> Foo = <Foo as From<u32>>::from; 

Which version of <Foo as From<u32>>::from; should be stored in foo_from? In this case there two versions with no semantic differences, but it is easy to imagine a case where there are multiple different implementations with different semantics.

2 Likes

#4

In addition to the above, there’s no way in rust syntax to exactly specify Tail and Head.

let y = 0u32.into(); is ambiguous in a very similar way, but it can be disambiguated by writing let y = Into::<i32>::into(0u32); explicitly.

The most specific AB::from(thing) can get is From::<Other>::from(thing). This does not, and cannot, specify Head and Tail, so there’s no way to choose a specific pair to use…

0 Likes