Generics defined for generic members that implement a trait

I want to implement StructTop<u8>, but the code does not compile:

trait Trait<N> {
    fn foo(&self) -> N;
}

struct StructBottom<N> {
    phantom: PhantomData<N>,
}

impl Trait<u8> for StructBottom<u8> {
    fn foo(&self) -> u8 {
        42
    }
}

struct StructTop<N> {
    struct_bottom: StructBottom<N>,
}

impl<N> StructTop<N> {
    fn bar(&self) -> N {
        // Error: method not found in `StructBottom<N>`
        self.struct_bottom.foo()
    }
}

Is there a way to achieve this, without boxing the struct_bottom as a Box<dyn Trait<N>> or making struct_bottom another generic parameter?

You will have to declare that you require Trait<N> to be implemented. N can be pretty much anything, after all. If you add where StructBottom<N>: Trait<N> to the function or impl block that requires it, foo should become usable.

2 Likes

StructBottom<N> only implements Trait<N> when N = u8. (And Rust only allows you to make use traits which are actually implemented, backed up by the trait bounds. Unlike some template systems.)

So use one of these approaches.

-impl<N> StructTop<N> {
+impl<N> StructTop<N> where StructBottom<N>: Trait<N> {
-impl<N> StructTop<N> {
-    fn bar(&self) -> N {
+impl StructTop<u8> {
+    fn bar(&self) -> u8 {
1 Like

Thanks, i didn't know that one could add trait bounds on things that are not just generic parameters.

Thank you, too.

That's possible but not always feasible because of #20671: it makes where “viral” since you have to copy-paste that where bound everywhere.

1 Like

Trait bounds don't constrain the left-hand side; they constrain any free type variables that appear in the where clause, whether they stand alone, embedded in a more complex type expression, and regardless of whether they are on the LHS or RHS of a bound.

This is similar to algebra, where the equation x + 2 = 3 doesn't constrain the numbers 2 or 3. It constrains the variable x. The equation doesn't mean "re-define all of math so that x + 2 = 3 for all x". It instead means "x must be 1", even if you exchange the two sides of the equality, and even if you write it in a more roundabout way such as 1 + x + 1 = 5 - 2.

1 Like

It's easy to assume that the where clause can't access anything other than the type parameters (and maybe Self), especially if you learn it as an alternative to the <T: Blah> syntax. It took a while for that puzzle piece to fully fall into place for me as well.

It's basically a list of general constraints or assumptions that need to hold for the implementation to work. It's powerful, but of course also opens up for some amount of viral spaghetti.

I think my favorite learning about how they work, and something people sometimes misunderstand, is that you don't always need to list all of the transitive requirements. Let's say your StructBottom also had a generic implementation with its own requirements. You don't need to repeat them for StructTop, unless you specifically need them there too.

Here's a modified version of the playground above, as an example, where any type that converts from u8 can be used. StructTop doesn't need to care about that, as long as Trait<N> is implemented.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.