Feature Request: Interrelated Traits

Hi! I'm ANoobyBird, and I'm a newbie in rust, as well as programming as a whole, so I try my best to articulate clearly, and please pardon me and point out as you may if you find this post to be ambiguous or nonconstructive in any manner, or if there's already a way to achieve what is suggested in rust which I haven't noticed.

I'd like to suggest that, there should be a mechanism for a trait B to be interrelated to another A by stipulating that if one is to implement a type T for B, it surely must also implement A, in a specific way. I must clarify that what I'm talking about is not a supertrait, a supertrait only puts the constraint but does not "know" how to satisfy the constraint.

For a really very rough and trivial and hopefully still clear enough illustration,

pub trait A {
  fn method_a1(&self); 
  fn method_a2(&self) -> usize; 
} 

pub trait B { 
  fn method_b1(&self); 
  fn method_b2(&self) -> usize; 
} 

// Imaginary syntax 
derive A for B {
  fn method_a1(&self) {
    self.method_b1(); 
  } 

  fn method_a2(&self) -> usize { 
    self.method_b2()
  }
}

// Now, any concrete type which has implemented B, 
// will also automatically implement A. 
// Something like that.

So, what I'm suggesting is essentially the ability for a trait B to derive another, say A, instead of having to implement A as a supertrait for every concrete type T.

Btw, of course such a derive may also be optional, depending on the specific design, say enabled only by writing #[derive(A)] specifically above a type which has implemented B, but that's the specifics, all I'm suggesting here is the possibility for a trait itself, not just a type, to be able to automatically derive another trait.

Thank you for reading!

And thank steffahn, who pointed out a mistake in the original post which is now edited.

Maybe you’re looking for a generic/blanket impl?

pub trait A {
    fn method_a1(&self);
    fn method_a2(&self) -> usize;
}

pub trait B {
    fn method_b1(&self);
    fn method_b2(&self) -> usize;
}

// Actual syntax
impl<T: ?Sized> A for T
where
    T: B,
{
    fn method_a1(&self) {
        self.method_b1();
    }

    fn method_a2(&self) -> usize {
        self.method_b2()
    }
}

// Now, any concrete type which has implemented B,
// will also automatically implement A.

(playground)


Note that your example code and description are not quite coherent / the roles of A and B appear to be switched:

“Deriving” the implementation for A from the implementation of B will have the effect that “every implementor of B also implements A”, not the other way around like in “if one is to implement a type T for A , it surely must also implement B”.

Thank you, steffahn! For pointing out the helpful usage of a blanket implementation; and indeed the code and the description have flipped roles for A and B, which I'm gonna go edit and correct.

But it turned out that the blanket implementation also has limitations, it may only be done when A and B are defined in the same module, which, in at least some if not many use cases, may be regarded as somewhat limiting. (Actually I really need several traits to be put in different modules.) If B and a third trait, say C, which also derives A, are defined elsewhere, the technique would no longer work as rustc would report the error code E0119 (conflicting implementation):

pub mod a { 
  pub trait A { ... }
}

pub mod b { 
  // import A 

  pub trait B { ... } 

  impl<BGeneric: B> A for BGeneric { ... }
} 

pub mod c {
  // import A

  pub trait C { ... } 
 
  impl<CGeneric: C> A for CGeneric { ... }
}

The above would produce E0119, and after doing some looking up, I think it's forbidden by the "orphan rule". And rustc seems to understand BGeneric and CGeneric as "the generic for all types" regardless of the constraints put right behind them.


And P. S. To add to the post, though there is a workaround to bypass the orphan rule, namely wrapping a generic into a tuple struct, for e. g., wrapping B in a struct BWrapper<T: B>(T), the workaround still poses inconvenient problems in a blanket implementation when things get slightly complicated, like, say, there are associated types in the supertrait dependent on Self.

pub mod a { 
  pub trait Assoc<T> { ... } 
 
  pub trait A { 
    type Assoc: Assoc<Self>; 
    // Other stuff 
  }
}

pub mod b { 
  pub trait B { ... } 

  pub struct BWrapper<T: B>(T); 

  impl<T: B> BWrapper<T> for A { 
    // I mean, this may still be resolved, but 
    // probably in a very messy fashion.
    type Assoc = ?
  }
}

It surely will involve lots of boilerplate to get around, so still I think it's plausible and to some extent necessary and natural for rust to enable (but of course not enforce) the "interrelations" or "derivations" between traits in general, no matter what form it might take, since in many enough (though not all) theoretical as well as practical cases, it only makes sense if a supertrait is "derived" from a subtrait in a particular way.

There are certainly limitations of blanket implementations currently in the language that seem unnecessarily restrictive. Your example with three traits is a canonical example what I’d have in mind for something that feels too restrictive, and IMO this kind of code really should (I hope) eventually be allowed in Rust. Having the traits in different crates is also a good example. (E.g. a trait A from an external crate, then define a trait B and an impl<T: B> A for T locally.)

In order to avoid overlap, the compiler would of course still need to track these blanket-impl relations; for the example of 3 traits, it would need to ensure that no type implements both B and C, and for the case with an external crate that I mentioned above, there’d have to be restrictions such as “you cannot implement B for any external types anymore” (because such an impl would imply an implementation of A [an external trait] for an external type), or “any crate depending on yours cannot have a type implement both A and B”.

I’m not sure if I’ve seen prior discussion on this kind of topic (I feel like I might have seen it somewhere.)

I hope so too. And yeah, you would be right that there are other discussions on similar topics, since I have come across them while searching online for a solution to a related problem in a little project I'm working on, though not necessarily on this exact forum.

Eventually I think it's safe to conclude that, the essence of all the problems of this sort is, that the variety of relationships between traits in rust are not yet completely nor perfectly captured by the currently existent mechanisms (such as declaring B as a subtrait of A or blanket-implementing A) offered by rust. And the idea of "derivation" or whatever term you find reasonable is only meant to better capture the said variety, and it may still not be enough, but capturing "the variety of relationships between traits" should be a direction to work on.