Experimenting with generic TryFrom, troubles occur


#1

I’m trying to implement a generic TryFrom implementation to convert a pair of integers to a (potentially) narrower type. The basic code looks like this:

type ParsedComponent = i32;
type ParsedOffset = i32;

impl<'a, T, U> TryFrom<RawReferenceComponent<'a>> for ReferenceComponent<T, U> 
    where
        T: TryFrom<ParsedComponent>,
        <T as TryFrom<ParsedComponent>>::Error: Into<Error>,
        U: TryFrom<ParsedOffset>,
        <T as TryFrom<ParsedOffset>>::Error: Into<Error>
{
    type Error = Error;

    fn try_from(component: RawReferenceComponent<'a>) -> Result<Self> {
        use self::RawReferenceComponent::*;
        Ok(match component {
            Index(index) 
                => ReferenceComponent::Index(index.try_into()?),
            ThisOffset(offset)
                => ReferenceComponent::ThisOffset(offset.try_into()?),
            _   => unimplemented!(), // Omitted for brevity
        })
    }
}

However, I’m getting an error I don’t understand:

error[E0277]: the trait bound `T: std::convert::From<i32>` is not satisfied
   --> src/reference/relative.rs:122:1
    |
122 | / impl<'a, T, U> TryFrom<RawReferenceComponent<'a>> for ReferenceComponent<T, U>
123 | |     where
124 | |         T: TryFrom<ParsedComponent>
125 | |         <T as TryFrom<ParsedComponent>>::Error: Into<Error>,
...   |
155 | |     }
156 | | }
    | |_^ the trait `std::convert::From<i32>` is not implemented for `T`
    |
    = help: consider adding a `where T: std::convert::From<i32>` bound
    = note: required because of the requirements on the impl of `std::convert::TryFrom<i32>` for `T`

Why would I need a From bound on T in order to satisfy TryFrom? If I had such a thing, I wouldn’t be bothering with TryFrom in the first place…


#2

In these lines:

        T: TryFrom<ParsedComponent>,
        <T as TryFrom<ParsedComponent>>::Error: Into<Error>,
        U: TryFrom<ParsedOffset>,
        <T as TryFrom<ParsedOffset>>::Error: Into<Error>

it looks like the last line should start with <U as TryFrom<ParsedOffset>> instead of <T...>.

The error message is definitely misleading; it looks similar to this issue.


#3

MWE: https://play.rust-lang.org/?gist=fb3342f1e3a13d20fa9c0acd7d8397b6&version=nightly&mode=debug

I don’t know compiler internals that well, but I’m guessing that Rust cannot see the link between

T: TryFrom<ParsedComponent>

and

<T as TryFrom<ParsedComponent>>::Error: Into<Error>

And so Rust is stuck trying to figure out why you’re claiming that T : TryFrom<ParsedComponent> is actually satisfied.

In more detail, what happens is:

  • In index.try_into() you’re using u32 : TryInto<T>
  • This trait impl was never directly defined
  • So Rust concludes it must come from the generic implementation with requirement T : TryFrom<u32>
  • It doesn’t realise that this trait impl is necessarily satisfied; it doesn’t find a definition
  • So Rust concludes it must come from the generic implementation on TryFrom with requirement T : From<u32>
  • It cannot find this, and there is also no generic implementation for this trait.

Instead of writing the bounds separately, you must combine them and add a new type variable: T: TryFrom<ParsedComponent, Error=TE>:

impl<'a, T, U, TE, UE> TryFrom<RawReferenceComponent<'a>> for ReferenceComponent<T, U> 
    where
        T: TryFrom<ParsedComponent, Error=TE>,
        TE: Into<Error>,
        U: TryFrom<ParsedOffset, Error=UE>,
        UE: Into<Error>

Then you must also explicitly convert the errors using .into():

ReferenceComponent::Index(index.try_into().map_err(Into::into)?)

Complete fixed MWE: https://play.rust-lang.org/?gist=40a0ab79d3bc944d8566984a27fe723d&version=nightly&mode=debug


#4

Here is a version that continues to use original code, more or less.

There’s no need for extra type parameters for the associated Error type - using normal projections works fine.

I think the issue is the compiler cannot see the link in T::Error: Into<Error>, but does understand the inverse Error: From<T::Error>. I find this a bit strange - presumably T::Error: Into<Self::Error> would be more convincing but this causes an overflow in requirement satisfying - not sure if that’s a bug or not.


#5

Cool find!


#6

Oh, wow, thanks for saving my butt again.