#Trait with method returning another trait

13 messages · Page 1 of 1 (latest)

junior dragon
#

Can somebody please help me with this example. My target is to have trait TraitObjectBuilder with method build_objects which return another generic which implement trait MyObject. Link to same code on playground is here.

Code

// ----- Traits -----

trait TraitObject {
    fn foo(&self);
}

trait TraitObjectBuilder {
    fn build_objects<T>(&self) -> T
    where
        T: TraitObject;
}

// ----- Structs -----
struct MyObject {}

impl TraitObject for MyObject {
    fn foo(&self) {
        println!("Hello");
    }
}

struct MyObjectBuilder {}

impl TraitObjectBuilder for MyObjectBuilder {
    fn build_objects<T>(&self) -> T
    where
        T: TraitObject,
    {
        MyObject {}
    }
}

// ----- Enums -----

enum Something<T>
where
    T: TraitObjectBuilder,
{
    One,
    Two,
    Three(T),
}

fn main() {
    let a = MyObjectBuilder {};
    let b = Something::Three(a);

    println!("Hello, world!");
}

Error:

  --> src/main.rs:29:9
   |
25 |     fn build_objects<T>(&self) -> T
   |                      -            - expected `T` because of return type
   |                      |
   |                      this type parameter
...
29 |         MyObject {}
   |         ^^^^^^^^^^^ expected type parameter `T`, found struct `MyObject`
   |
   = note: expected type parameter `T`
                      found struct `MyObject`
bitter warren
# junior dragon Can somebody please help me with this example. My target is to have trait `Trait...

Your problem is that method generics are always chosen by the caller, never by the implementor. The way you've written build_objects, a caller can take MyObjectBuilder and do my_object_builder.build_objects::<ADifferentObject>(), and if they do so then you have to return ADifferentObject. This is what the compiler means when it says it expected T: You have to return whatever the caller-chosen T is

#

If you meant to say that a given TraitObjectBuilder can only construct one T, and the implementor chooses which one it is, you want an associated type.

If you meant to say that a given TraitObjectBuilder can construct multiple Ts, but only ones chosen by the implementor, then you want to put the generic on the trait.

junior dragon
bitter warren
# junior dragon Thank you for your comment. Can you please share how should look code for both v...

Associated type:

trait TraitObject {
  fn foo(&self);
}
trait TraitObjectBuilder {
  type Output: TraitObject; //this is called an associated type
  fn build_object(&self) -> Self::Output;
}

struct MyObject; //you don't need `{}` to create a unit struct
impl TraitObject for MyObject {...} //implementation body irrelevant for this example

struct MyObjectBuilder;
impl TraitObjectBuilder for MyObjectBuilder {
  type Output = MyObject;
  fn build_object(&self) -> Self::Output {
    MyObject
  }
}
```Trait-level Generic:
```rs
trait TraitObject {
  fn foo(&self);
}
trait TraitObjectBuilder<T: TraitObject> {
  fn build_object(&self) -> T;
}

struct MyObject;
impl TraitObject for MyObject {...}

//I'm adding another type to showcase that with generics you can use more than one.
struct MyObject;
impl TraitObject for MyObject {...}

struct MyObjectBuilder;
impl TraitObjectBuilder<MyObject> for MyObjectBuilder {
  fn build_object(&self) -> MyObject {
    MyObject
  }
}
impl TraitObjectBuilder<AnotherObject> for MyObjectBuilder {
  fn build_object(&self) -> AnotherObject {
    AnotherObject
  }
}
```Due to a few reasons related to storing objects in structs, if you expect that an implementor of `TraitObjectBuilder` will only ever want to build one type of object, then you should use the associated type: using a generic you don't need isn't _wrong_ and has no performance cost, but it will result in having to add more avoidable generics all over the place.
junior dragon
# bitter warren Associated type: ```rs trait TraitObject { fn foo(&self); } trait TraitObjectB...

One last thing, when I tried your second option and use it in enum I getting error. I had tried sever options with new type R but without success. What am I doing wrong?

// ----- Traits -----

trait TraitObject {
    fn foo(&self);
}
trait TraitObjectBuilder<T: TraitObject> {
    fn build_object(&self) -> T;
}

// ----- Structs -----

struct MyObject;
impl TraitObject for MyObject {
    fn foo(&self) {
        println!("ddd")
    }
}

struct AnotherObject;
impl TraitObject for AnotherObject {
    fn foo(&self) {
        println!("sdd")
    }
}

struct MyObjectBuilder;

impl TraitObjectBuilder<MyObject> for MyObjectBuilder {
    fn build_object(&self) -> MyObject {
        MyObject
    }
}
impl TraitObjectBuilder<AnotherObject> for MyObjectBuilder {
    fn build_object(&self) -> AnotherObject {
        AnotherObject
    }
}

// ----- Enums -----

enum Something<T, R>
where
    T: TraitObjectBuilder<R>,
    R: TraitObject,
{
    One,
    Two,
    Three(T),
}

fn main() {
    let a = MyObjectBuilder {};
    let b = Something::Three(a);
}

bitter warren
# junior dragon One last thing, when I tried your second option and use it in enum I getting err...

You can't leave a type parameter unused in a type, and R is unused in that enum.

No, using it in a trait bound doesn't count, you have to actually use it in the enum contents.

Usually when you have this error it's because you didn't actually want a generic, and should have gone for an associated type, but if you do need a generic you can add a "use" for no runtime cost by putting a PhantomData<R> in the enum.

junior dragon
#

I had tried this but during match I get error/I need to specify type of obj. But when it is specified in b I would expect, that it would not be necessary.


enum Something<T, R>
where
    T: TraitObjectBuilder<R>,
    R: TraitObject,
{
    One,
    Two,
    Three(T),
    A(PhantomData<R>),
}

fn main() {
    let a = MyObjectBuilder {};
    let b = Something::Three::<MyObjectBuilder, AnotherObject>(a);

    match b {
        Something::Three(x) => {
            let obj = x.build_object();
        }
        _ => {}
    }
}

bitter warren
#

By the time you have x, it's a MyObjectBuilder

#

Which can build multiple possible objects.

#

Frankly this entire setup is sounding ever more peculiar. You may want to ask in a more visible place, such as #rust-discussions-1, to get more eyes on your question. I get the feeling you're doing something weird, but I can't be sure if it actually is