How to implement a trait for both by-value and by-reference argument?

Hi! I have a problem with the system of types.

note: downstream crates may implement trait `AsNumber` for type `&_`

I understand what is the reason, but have no clue how to solve it. Goal to teach function from2 accept argument by reference. Currently the function can accept argument only by value.

By value:

    let src = Struct1 { a : 13 };
    let got = i32::from2( src );
    let exp = 13;
    assert_eq!( got, exp );

By reference:

    let src = Struct1 { a : 13 };
    let got = i32::from2( &src );
    let exp = 13;
    assert_eq!( got, exp );

Uncommenting would solve the problem

// // ! error[E0119]: conflicting implementations of trait `From2<&_>`
// impl< Target > From2< &Target >
// for Target
// {
//   fn from2( original : &Target ) -> Self
//   {
//     < Self as From2< Target > >::from2( *original )
//   }
// }

But it causes the problem mentioned above. Implementing AsNumber for references is not an option in the associated circumstances.

Playground

There's no direct way around the orphan rules, but maybe there's some other way to satisfy your end goal. But, I'm not sure exactly what that is.

This can't work unless Target: Copy because you're trying to move out from behind a shared reference:

impl<Target: From2<Target>> From2<&Target> for Target {
    fn from2(original: &Target) -> Self {
        <Self as From2<Target>>::from2(*original)
    }
}

Are your implementing types always Copy?

1 Like

Here I elaborate on why I asked that / what I was thinking for an approach.

Method call resolution makes it ergonomic to call methods through multiple layers of references. However, for this to apply to your use case, you would need

  • A method for the conversion, i.e. a function that takes self
    • That is, something like Into, not like From
  • Self to be Copy, since as_number consumes Self
    • But it seems from your OP you'll need this anyway, as per my previous post

For the first, we can just define a method within AsNumber itself with a default implementation:

    fn into2<Other: AsNumber>(self) -> Other where Self: Sized {
        Other::make(self.as_number())
    }

And if we have the second, so we can move from underneath references, then method resolution will work automatically through any number of references.

Playground.


Does the Copy requirement make sense? That's going to depend on your use case, but with the information in this thread so far, I would say so. Let's take a closer look at your original trait:

pub trait AsNumber {
    fn make(src: i32) -> Self;
    fn as_number(self) -> i32;
}

This is pretty much the same as:

pub trait AsNumber: From<i32> + Into<i32> {}

And without any further context, I would say this only make sense for types which basically "are" an i32.

Adjusted playground.

1 Like

Hi @quinedot thanks for the answer, I was trying to understand it and reproduce.
Yes, types are always Copy.

Playing with the code, I encountered behavior of the compiler I can't explain. Introducing a new trait without even implementing it for any structure breaks the program:

pub trait FromInto<T>: Into2<T> {
    fn into3<Other: From2<T>>(self) -> Other {
        Other::from2(self.into2())
    }
}

Breaks the program with strange error

the trait `AsNumber` is not implemented for FromInto<T>

My understanding there should be no error and if any it should appear after an attempt to implement it for a structure. What is the logic behind the error? What to read to disclose the mystery behind the problem.

Playground

The issue comes from the declaration of Into2<T>:

pub trait Into2<T>: Sized
where
    T: From2<Self> + Sized,
{ ... }

For Into2<T> to be valid as a trait, T must be From2<Self> + Sized. So if we want Into2<T> to appear as a supertrait of FromInto<T>, we must repeat that bound (Rust Playground):

pub trait FromInto<T>: Into2<T>
where
    T: From2<Self> + Sized,
{ ... }
1 Like

Aha. I see. Does not it mean error log is not informative in this case?
Thanks.

I was trying to rephrase your solution, uncoupling local trait AsNumber from generic, moving into/from trick into a separate trait. But in my case it asks to specify type:

   |
17 |     let got : i32 = ( &&&src ).into3();
   |                                ^^^^^ cannot infer type for type parameter `T` declared on the trait `FromInto

@quinedot Maybe you see a problem?

Playground

The error is misleading and incorrect in some sense; see issue 40120. Applying the suggestions does fix the issue, but arguably in a suboptimal way.

However, it still contains useful information even if you consider it to be incorrect. Let's start from here and try to trace the first error message's logic. It's pointing out this blanket implementation:

89 | impl<Original, Target> From2<Original> for Target

Which is indeed dependent on AsNumber:

where
    Original: AsNumber,
    Target: AsNumber,

And it says that's because of the declaration of Into2:

28 | pub trait Into2<T>: Sized
   |           ----- required by a bound in this
29 | where
30 |     T: From2<Self> + Sized,
   |        ^^^^^^^^^^^ required by this bound in `Into2`

And Into2 is a (required) supertrait of FromInto. It suggests binding Self and T by AsNumber.


If we consider these notes backwards starting from this trait we've declared, FromInto, it goes something like

  • FromInto<T> requires Into2<T>
  • Into2<T> in turn requires that
    • T: From2<Self>

This is probably where the diagnosis should stop, but it instead continues and tries to see what it would take for T: From2<Self> + Sized and finds the blanket implementation. The requirements of the blanket implementation, translated, are

    • T: AsNumber
    • Self: AsNumber

And this is the source of the suggestions. If you apply the suggestions, it does in fact fix the errors.

  • The blanket implementation of From2 shows that T: From2<Self>
  • This satisfies the where clause on trait Into2
  • This satisfies the supertrait bound on trait FromInto

I'll circle back to the commented-out implementation of FromInto, but first...


...let's back up. I asserted that the diagnosis probably should have stopped here:

  • FromInto<T> requires Into2<T>
  • Into2<T> in turn requires that
    • T: From2<Self>

And the reason is that, like described in the issue I linked above, you can satisfy these bounds directly, and this is often more general than relying on the "far away" blanket implementation. So let's update our new bounds to use this "closer" requirement:

 pub trait FromInto<T>: Into2<T>
 where
-    Self: AsNumber,
-    T: AsNumber,
+    T: From2<Self>,
 {

Yep, that works to.


What about the commented out implementation of FromInto? So long as you add the same restrictions as you did to the trait declaration, it works now too.

A lot of it is just experience; once you've encountered it enough, you'll probably build up a feel for these situations, and an ability to sort of trace out what logic chain the compiler was going down when it gave you the error that it did (like I wrote up above).

However, there is one piece of critical knowledge to help understand these bounds that you have to carry around on your trait declarations and implementations:

  • Implied bounds need not be repeated
  • But non-implied bounds have to be repeated
  • Supertrait bounds are implied
  • But bounds are parameters (like the T in Into2<T>) are not implied (yet)
    • And these are the ones you have to "carry around"

So if we consider this:

pub trait FromInto<T>: Into2<T>

We can automatically assume any supertrait bounds of

pub trait Into2<T>: Sized
where
    T: From2<Self> + Sized,

And there is one: We can assume that Self: Sized. We don't have to repeat that bound, even though it's required. However, we cannot assume that

  • T: From2<Self>

Because that's not a supertrait bound. (We can't assume that T: Sized either, but because we didn't use T: ?Sized, it has that bound anyway. Yes, this implied bounds can interact in confusing ways.)

And so that's why you had to add the bound to your trait declaration in order for it to be consistent.


There's a plan to expand implied bounds to cover bounds on type parameters such as this. If they pull it off [1], it will remove the need for "carrying around" these non-supertrait bounds like you've encountered here. You can learn more by reading the RFC; e.g. other bounds that are already implied today.

See also this discussion.

I don't know if it's discussed anywhere in the book or the like.


  1. anything messing with inference tends to be hard to implement in a non-breaking way ↩ī¸Ž

2 Likes
pub trait FromInto<T>: Into2<T>
where
    T: From2<Self>,
{
    fn into3<Other: From2<T>>(self) -> Other

There's two much indirection between T and Other; you can get an Other out of Self for multiple types of T. For example, these both work:

        let got: i32 = <Struct1 as FromInto<Struct1>>::into3(src);
        let got: i32 = <Struct1 as FromInto<i32>>::into3(src);

Coherence will prefer inherent methods to trait methods, but in the case of multiple conflicting trait methods, you have to be specific -- it won't arbitrarily choose one for you. There were multiple choices for T which could be applied here, so it bailed.

3 Likes

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.