Using any struct that implements a trait in another struct


#1

Hi

I have done the following:
I have a trait that is implemented by several structs. Now the trait has a function run.

pub trait SomeTrait {
    fn foo(&self) -> ...;
}

pub struct SomeStruct {}

impl SomeStruct for SomeTrait {
    fn foo(&self) -> ... { .... }
}

Now, i want to use this struct as a member of another struct in such a way that i could pass any struct to it as long as the struct implements the trait

pub struct Bar <'a> {
    abc: &'a str,
    def: T, // <--- Looking for something to replace this with
}

impl <'a> Bar <'a> {
    pub fn some_function(&self)  {
          self.def.foo();
    }
}

(ignore stuff such as lifetimes from the code as these are there for other reasons)

But this isn’t possible. Is there a way I could pass in saying to accept any struct that implements the trait? (as i typed this I realised this looks unlikely)

Or should I change this to use traits or any other constructs instead?

Thanks


#2

Sure, just like:

pub struct Bar <'a, T: SomeTrait> {
    abc: &'a str,
    def: T, // e.g. with `Bar<SomeStruct>`, `T` will be `SomeStruct` here
}

#3

Alternatively, if you don’t want Bar parameterized, you can use a trait object, like def: Box<SomeTrait> or def: &'a SomeTrait depending on whether you want to own the value.


#4

As another alternative, if you add the + ?Sized bound to T, and you’re using Bar through a pointer of some kind, you can coerce any Bar<T> into a Bar<SomeTrait> and stop the propagation of type parameters.

Playground link


#5

Note: as a general recommendation, don’t put trait bounds directly on a struct when it is avoidable (because such bounds are forced to appear all over the code). Instead, put it on the functions or impls which require that trait.

pub struct Bar<'a, T> { // Note: No mention of SomeTrait here
    abc: &'a str,
    def: T,
}

impl<'a, T> Bar<'a>
where T: SomeTrait   // <-- this is where we do it
{
    pub fn some_function(&self)  {
          self.def.foo();
    }
}

There are only three instances where you are required to put a bound in the struct itself:

  • when the object has a field of an associated type
    • (see std::borrow::Cow which has a ToOwned bound because it actually may contain the associated type <B as ToOwned>::Owned)
    • (similarly Peekable holds a <T as Iterator>::Item)
  • when that trait is used in the destructor
    • (examples are rare; I could only find one in the standard library; that type needs Ord because its drop impl calls a method that requires Ord)
  • when you carry another struct that has a bound. (see? It’s infectous!)

#6

This should go away with https://github.com/rust-lang/rfcs/pull/2089. I actually find bounds on impls backwards when the struct can only function (or intended to) with a certain bound.


#7

Man, that ergonomics initiative just keeps surprising!

Hmm, go figure, though, we’ll still need K: Eq + Hash + Clone everywhere we want to use HashMaps, since the standard library types obviously cannot add the bounds to the type without breaking backwards compatibility…


#8

Hi

Thanks for all the answers. Ended up using @ExpHP’s approach and it worked :slight_smile:

Cheers