Same generic parameter for struct and method with trait

Hi there, I am trying to solve an issue where I have the same type parameter twice and rust is returning errors. My code looks like the following

trait MyTrait<OT> where OT: OtherTrait {
    fn my_method<OOT>(input: OOT) -> OT where OOT: OtherOtherTrait;
}

struct MyStruct<OOT> where OOT: OtherOtherTrait {
    field: OOT
}

impl<OOT> OtherTrait for MyStruct<OOT> where OOT: OtherOtherTrait {}

struct OtherStruct {}

impl<OOT> MyTrait<MyStruct<OOT>> for OtherStruct where OOT: OtherOtherTrait {
    fn my_method<OOT>(input:OOT) -> MyStruct<OOT> where OOT: OtherOtherTrait {
        MyStruct {
            field: input
        }
    }
}

trait OtherTrait {}

trait OtherOtherTrait {}

Generally, I want my_method to return a struct that must have the OtherOtherTrait implemented on it. And the input for my_method will be a struct that also has the same trait applied to it. my_method will always be returning a struct with OtherTrait implemented on it, however it may not be dependant on the OtherOtherTrait.

In trying to fix this, I am getting errors about how the OOT from my_method has already been defined, and when I try renaming this to OOT2, I get errors about the return type, where it is expecting OOT but getting OOT2.

How do I tell rust that these are the same type parameters?

Thanks in advance!

You could use an associated type

trait MyTrait<OT> where OT: OtherTrait {
    type OOT;
    fn my_method(input: Self::OOT) -> OT where Self::OOT: OtherOtherTrait;
}

struct MyStruct<OOT> where OOT: OtherOtherTrait {
    field: OOT
}

impl<OOT> OtherTrait for MyStruct<OOT> where OOT: OtherOtherTrait {}

struct OtherStruct {}

impl<OOT> MyTrait<MyStruct<OOT>> for OtherStruct where OOT: OtherOtherTrait {
    type OOT = OOT;
    fn my_method(input:OOT) -> MyStruct<OOT> where OOT: OtherOtherTrait {
        MyStruct {
            field: input
        }
    }
}

trait OtherTrait {}

trait OtherOtherTrait {}
2 Likes

@steffahn thanks for the response, this is really helpful.

Another problem that I have got to now if how to implament this for when MyStruct isnt dependant on OOT? How do I define the associated type in this case? Would I need to add any phantom data?

If it’s not dependent, then you’ll need another way to specify which OOT types your OT supports. For MyStruct<OOT> it’s only the OOT that appears as a parameter; for MyOtherStruct it may be all OOT: OtherOtherTrait.

You could do this through a new trait or through MyTrait or possibly through OtherTrait, in the latter cases by adding an OOT parameter to the trait. Let’s look at the example of doing this through MyTrait. Then one could write

trait MyTrait<OOT, OT>
where
    OOT: OtherOtherTrait,
    OT: OtherTrait,
{
    fn my_method(input: OOT) -> OT;
}

struct MyStruct<OOT>
where
    OOT: OtherOtherTrait,
{
    field: OOT,
}

struct MyOtherStruct {}

impl<OOT> OtherTrait for MyStruct<OOT> where OOT: OtherOtherTrait {}

impl OtherTrait for MyOtherStruct {}

struct OtherStruct {}

impl<OOT> MyTrait<OOT, MyStruct<OOT>> for OtherStruct
where
    OOT: OtherOtherTrait,
{
    fn my_method(input: OOT) -> MyStruct<OOT> {
        MyStruct { field: input }
    }
}

impl<OOT> MyTrait<OOT, MyOtherStruct> for OtherStruct
where
    OOT: OtherOtherTrait,
{
    fn my_method(input: OOT) -> MyOtherStruct {
        MyOtherStruct {}
    }
}

trait OtherTrait {}

trait OtherOtherTrait {}

At this point, one can also note that, at least in the context of this example code, the trait bounds in the definition of MyTrait are relatively useless. They restrict users and implementors, but they won’t really give you anything (unless in the real use-case the function signatures of the MyTrait trait use e.g. associated types from OtherTrait or OtherOtherTrait, or use methods of them in default method implementations, etc…

Without such bounds, the code would look identical except for

trait MyTrait<OOT, OT> {
    fn my_method(input: OOT) -> OT;
}

Similarly, trait bounds on structs are rarely used, unless the struct’s field types would e.g. depend on some associated types from that trait, or unless the struct’s Drop implementation needs methods from the trait. So one could also simplify to

struct MyStruct<OOT> {
    field: OOT,
}

Rust Playground


Also, all of these remarks are at least a little a bit thrown into the dark / done blindly, as the code we’re talking about looks as if it’s quite far abstracted from “actual” code, so it’s harder to get a feeling for the intention/ideas behind the code’s structure.

1 Like

@steffahn thanks again. This was really helpful and helped solve my problem. Apologies for the abstractness, just trying to not include any unnecessary information and keep the examples concise.

Once again, many thanks!!