Generic Trait of Trait of Trait

I'm trying to implement abstractions upon existing abstractions.

In the following code:

  • I've got Trait1 defined
  • I've got Trait2 defined upon Trait1
  • I want to define Trait3 upon Trait2
  • I want to create Trait3's new method receiving a Vec of Trait2 implementations
  • I want each element from Vec of Trait2 to have any Trait1 implementation.
pub trait Trait1 {}
pub trait Trait2<X> where X: Trait1 {}

pub trait Trait3 {
    /*
        wrong number of type arguments: expected 1, found 0
        expected 1 type argumentrustc(E0107)

        - > underlined "Trait2"
    */
    fn new (foo: Vec<Rc<impl Trait2>>) -> Self;

    /*
        the size for values of type `(dyn Trait1 + 'static)` cannot be known at compilation time
        doesn't have a size known at compile-time
        help: the trait `std::marker::Sized` is not implemented for `(dyn Trait1 + 'static)`
        note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced- 
        types.html#dynamically-sized-types-and-the-sized-trait>rustc(E0277)

        -> underlined "Trait2<Trait1>"
    */
    fn new (foo: Vec<Rc<impl Trait2<Trait1>>>) -> Self;

    /* no compile errors, but I think it fixes Trait1 to a single implementation */
    fn new<T>(functions: Vec<Rc<impl Trait2<T>>>) -> Self
    where
        T: Trait1;
}

Question:

  1. How can I implement new method, so that Trait1 will have any implementation?

How about this?

use std::rc::Rc;

pub trait Trait1 {}
pub trait Trait2<X> where X: Trait1 + ?Sized {}

pub trait Trait3 {
    fn new<T1: ?Sized, T2>(foo: Vec<Rc<T2>>) -> Self
    where
        T2: Trait2<T1>,
        T1: Trait1;
}

The use of ?Sized allows T1 to be unsized, so the user can choose T1 = dyn Trait1 if they wish multiple different ones. Of course, if you want to restrict it to always use dyn Trait1, you could do:

pub trait Trait3 {
    fn new<T2>(foo: Vec<Rc<T2>>) -> Self
    where
        T2: Trait2<dyn Trait1>;
}
2 Likes

What a fast answer!

But it still raises the following

   /*
   the trait `Trait1` cannot be made into an objectrustc(E0038)

   this trait cannot be made into an object...
   ...because associated function `new` has no `self` parameter
   */
   pub trait Trait3 {
      fn new<T1: ?Sized, T2>(foo: Vec<Rc<T2>>) -> Self
      where
         T2: Trait2<T1>, /* <-undelines here */
         T1: Trait1;
   }

It seems to be requiring Trait 1 implementation.

Then if I force impl syntax, it raises another one.

   /*
      `impl Trait` not allowed outside of function and inherent method return typesrustc(E0562)
   */
   pub trait Trait3 {
      fn new<T1: ?Sized, T2>(foo: Vec<Rc<T2>>) -> Self
      where
         T2: Trait2<impl T1>, /* <-undelines here */
         T1: Trait1;
   }

Actually, your code compiles with no errors.

Sorry for that!

It seems my minimal sample code leads to those for other reasons. I'll find out then I come back.

I've found out.

Trait1 cannot be made an object error raises within Trait3 when Trait1 has any method returning Self.

use std::rc::Rc;

pub trait Trait1 {
   fn foo () -> Self;
}
pub trait Trait2<X> where X: Trait1 + ?Sized {}

pub trait Trait3 {
    fn new<T1: ?Sized, T2>(foo: Vec<Rc<T2>>) -> Self
    where
        T2: Trait2<T1>,
        T1: Trait1;
}

This one eventually compiles

use std::rc::Rc;

pub trait Trait1 {
    // fn foo () -> Self;
}

pub trait Trait2<X> where X: Trait1 + ?Sized {

}

pub trait Trait3 {
    fn new(foo: Vec<Rc<dyn Trait2<dyn Trait1>>>) -> Self;
}

Maybe I should avoid having constructor patterns imposed by traits? lol

Another option is this, which says the constructor can only be called when the underlying type of Trait3 is known.

pub trait Trait3 {
    fn new<T2>(foo: Vec<Rc<T2>>) -> Self
    where
        T2: Trait2<dyn Trait1>,
        Self: Sized;
}

But yes, having constructors in traits is considered bad practice.

1 Like

Thanks for your help!

I've learnt good things.