Is there any way to accept multiple generic type parameters that implements different traits?

Hi everyone. I maybe confused here little bit or not able to fully understand the generic type parameter concepts.

How can I make my_function accept bar which implements the BarTrait?

#[derive(Debug)]
struct Foo;
#[derive(Debug)]
struct Bar;

trait FooTrait{}
trait BarTrait{}

impl FooTrait for Foo{}
impl BarTrait for Bar{}

fn my_function<P: FooTrait + ?BarTrait + std::fmt::Debug>(param: P) {
    println!("{param:?}");
}

fn main() {
    let foo = Foo{};
    let bar = Bar{};
    my_function(foo);
    my_function(bar);
}

Playground link

I am beginner. If possible, please, share some documentation to point me to the right direction. I highly appreciate your help. Thank you.

You can't make a generic function that accepts a parameter with one of two different traits, if that's what you mean to ask. Define one trait and have both types implement it.

If that doesn't serve your needs, please describe more of what you want to accomplish — not just empty trait declarations — so we can suggest a fitting solution.

2 Likes

You questions don't sound like a questions from the beginner. Rather they sound like someone who does classic mistake #6. Trying to write SomeOtherLanguage in Rust.

I'm not entirely sure whether you are trying to write Python in Rust, or trying to write C++ in Rust or maybe even Java in Rust… but what you are trying to achieve definitely doesn't make much sense in Rust.

Can you try to zoom out a bit, show the “business task” which you are trying to solve? Something not in terms of traits or generics, but higher level. To avoid the XY problem.

Because attempts to write some other language in Rust very quickly leads you nowhere: you try to adopt some kind of design pattern from the other language, do couple of steps in that solution and arrive at point where not only there are no solution for the narrow problem, but often question which you are trying to solve doesn't even make any sense in Rust.

E.g. your question about two traits doesn't make much sense because of how Rust deals with generics: they must be fully typechecked when they are defined. As in: everything which generic interface allows should be possible to actually use. And, more importantly: you have write the code of generic without trying to “look” on the type of argument passed.

If your argument implements either FooTrait or BarTrait then what can you do with that information? You couldn't “ask” for actual type thus you have no idea whether FooTrait or BarTrait is implemented. You couldn't use entities from FooTrait because type may end up not implementing it and also entities from BarTrait would be, similarly, not accessible. Thus it's unclear what can you use type implements either FooTrait or BarTrait but not necessarily both predicate for.

Some kind of marker trait without any content? Possible, I guess, but this definitely need to be discussed on higher level: why would you need such marker traits and what you are you trying to achieve in the end.

3 Likes

My intention was to implement a generic function which can accept two different structs with same structure but with different trait implementations. The other reason I stuck at this idea - I understood that the question mark ? in front of the trait name literally means "optional", thus I can list trait names with question mark which can tell compiler that the parameter "optionally" may have implemented that particular trait.

.. please describe more of what you want to accomplish — not just empty trait declarations — so we can suggest a fitting solution.

Well, I don't have an exact problem, I was just playing around some examples from the book and I was thinking "how about this", "how about that" patterns.

You questions don't sound like a questions from the beginner. Rather they sound like someone who does classic mistake #6 . Trying to write SomeOtherLanguage in Rust.

I think my mistake looks more closer to #3 than #6.

Thank you both for your time and attention. I should complete reading the book.

Sounds like you've got a plan. So, please don't take this as criticism or recommendations for what to do next, but just some further ideas that might be useful:

My intention was to implement a generic function which can accept two different structs with same structure but with different trait implementations.

It's entirely normal for a function to accept different implementations of a trait — that's what most generic functions do. But it is impossible for them to accept different traits. It will be useful to mind the distinction between "traits" and "trait implementations".

The other reason I stuck at this idea - I understood that the question mark ? in front of the trait name literally means "optional", thus I can list trait names with question mark which can tell compiler that the parameter "optionally" may have implemented that particular trait.

Right now, without specialization support (something still being worked on by the compiler authors), you can (almost[1]) never write a program that takes some different action based on whether a trait is implemented for some type. You can only write a program that either

  • doesn't care at all whether the trait is implemented for the type, or
  • fails to compile if the trait is not implemented.

  1. Look up “autoref specialization” if you really want to know. It cannot be used to write generic functions, only to do clever things with method calls. ↩︎

3 Likes

?Trait is not valid syntax. The only exception is Sized, and that is only because the type generic parameters on functions are assumed Sized by default, for ergonomic reasons. The semantics of ?Sized are "maybe isn't Sized", which is also the way all other traits naturally work. If you don't declare T: Trait, then maybe the type T does implement Trait, or maybe it doesn't, but in either case you can't rely on any properties of Trait to be true.

4 Likes

That sounds like you want to define a new trait and implement it for the two structs. Remember that you can define new trait for structs that are defined in a different module or crate.

1 Like

In that case you need one trait with two implementations. Think about trait as about interface in Java or Typescript.

You explain what things the generic function may do with your type and then implementation of trait explains how can it be done.

Rust is slightly unusual because, unlike Java, implementations of different interfaces exist separately from definition of data structure itself.

This, in particular means that you can easily create interface and implementation for any types which you don't even own (like i32 or String).

Of course if everyone would be able to add implementations of any traits for any types there would be chaos (or, more likely, link time errors when linker would find out dozen of conflicting implementations).

Thus there are some coherence rules: TL;DR version is that you must own either type or trait if you want to add implementation, that is you can implement your own traits for i32 or implement foreign trait for your struct Foo but you can't implement foreign trait for i32.

1 Like