Home > Software design >  How should I implement From/Into for my struct with one generic parameter bound by my trait?
How should I implement From/Into for my struct with one generic parameter bound by my trait?

Time:09-21

https://www.rustexplorer.com/b/pg3wqr

trait A {}

struct B<T: A>(T);

// this is ok
impl<T: A> From<T> for B<T> {
    fn from(t: T) -> Self { B(t) }
}

// this is not ok
impl<T: A> From<B<T>> for T {
    fn from(b: B<T>) -> Self { b.0 }
}

I get:

error[E0210]: type parameter T must be covered by another type when it appears before the first local type (B<T>)

This is not really helping (neither did searching this error message). What I wanted to achieve seems pretty straightforward. Why is this not allowed, and how can I work around the limitations?

https://stackoverflow.com/a/39186717/4876553 basically recommended the same thing, without the bound on the impl, which fails to compile in the same manner.

EDIT: I accidentally copy-pasted a WIP version of me trying to work around this; fixed back to the original example.

CodePudding user response:

Rust's orphan rules don't allow it. The exact covering semantics are complicated (if you're interested, this article is a good jumping-off point), but the basic idea is this:

Take all of the type and trait names passed to an impl in order, starting with the trait itself, then to the structure or enum we're writing an impl for, then any type arguments. So with the impl written as

impl<T> MyTrait<Bar, Baz, T> for Foo

we would write MyTrait, Foo, Bar, Baz, T (where T is a type variable).

Now, in this list we've written, find the first name that is defined in our own crate. If the trait is defined in our own crate, then that's the name. If Foo is, then take that one. Otherwise, keep going until you find one. If there are no names defined in the current crate, fail because we have an orphan instance. In this example, let's pretend MyTrait is defined somewhere else but Foo is our own. Then we have

MyTrait, Foo, Bar, Baz, T
         ^^^

Anything before the name we just found must be concrete, i.e. it can't be a generic type argument. In our example here, the only type argument is T, and it occurs after Foo, so it's fine.

(Note that I'm also glossing over covered types. That is, an impl on &Foo is, for the orphan purposes, considered an impl on Foo. This applies to references, Box, and several other built-in Box-like types; it never applies to types you define yourself)

Now let's take a look at your examples. Things get more complicated when we add generics, and I'm not going to go over all of the messy technical details here, so we'll try to keep it as simple as possible.

impl<T: A> From<T> for B<T>

Here, the trait is From, the type we're implementing is B, and our type argument to From is T. Our list is

From, B, T

Note that although we're implementing for B<T>, we say T is covered by the B type, so we only take the top-level B, not what's inside.

Now From is standard library, so we don't own it, but B is in our crate. So we have

From, B, T
      ^

Then we look for generic arguments. The only type argument here is T, and it appears after the B that we own, so we're good.

Now the other one.

impl<T: A> From<B<T>> for T

The trait is From, as before. The first type is T, a generic type argument. Then B<T> is next.

From, T, B

Again, we write B, not B<T>, since we only care about uncovered types.

From is standard library, and T is a type argument, so B is the only thing we own here.

From, T, B
         ^

But there's a type argument T before the first thing we own! That's a problem, so this fails by Rust's orphan rules.

The reason the rules are written this way is to prevent two sibling crates (i.e. crates who are not aware of each other) from accidentally implementing conflicting instances. If we allowed

impl<T: A> From<B<T>> for T

then someone else might come along one day and write

impl<T> From<T> for C

for some type C in their own crate. They're well within their rights to do so: C is their own type and the only generic argument occurs after C in their trait argument list. But then there are two candidate solutions for impl From<B<C>> for C. (Note that trait bounds, i.e. T: A are taken into consideration much later and are not considered at this point, since that would just make the already complex problem of trait resolution completely untenable).

CodePudding user response:

In your implementation you are using from(u: U). The way you have your code written U isn't a generic type because you haven't declared it in the impl block. The only generic type available is T. In the function you need to use a parameter that is of type B. You have declared B as a tuple struct with its 1 and only field being of generic type T which must implement trait A. So to get that t back out of B you need to call b.0 which is the syntax to get the value out of a tuple struct. You also need to make that field accessible so you need to change the struct definition to

struct B<T: A>(pub T);

and then change the impl block to this

impl<T: A> From<B<T>> for T {
    fn from(b: B) -> T { 
        b.0 
    }
}

The function definition could also return Self e.g. fn from(b: B) -> Self {...} because in this impl block Self is the same as T.

  • Related