I went through this feature chapter from rust book.
I didn't get, why we need this feature. I thought rust will stay away from inheritance.
Where can I find detail about feature ?
Why community thought this is useful feature ?
I went through this feature chapter from rust book.
I didn't get, why we need this feature. I thought rust will stay away from inheritance.
Where can I find detail about feature ?
Why community thought this is useful feature ?
A "supertrait" is not inheritance.
trait Sub: Super {}
is actually just syntax sugar for
trait Sub
where
Self: Super
{}
i.e., it's the same old regular trait bound that any other where clause would mean; it doesn't imply anything else. And you should be able to put bounds on any type variable (and any type in general), including Self
.
The syntactic sugar is not necessary, of course. One could just write out the Self constraint using the where
clause. However, this form of dependent relationship between traits is so common that it was deemed valuable to shorten it somewhat.
Specifically why it isn't a problem like in traditional OO inheritance is down to two things:
You can only inherit behavior, not state, so you don't get any of the slicing / diamond inheritance issues like in C++.
Impl blocks are completely separate, so Sprite::draw
and Gun::draw
are totally different methods. At the call site you have to import which trait you want use, and you can always fully qualify, like: Sprite::draw(&object)
if you want both in scope.
There is actually a traditional inheritance-like feature, though: Deref in std::ops - Rust allows treating an object of one type as if it was another, and it's recursive, with all the potential issues that inheritance can have. For that reason, it's only intended for use on smart pointers, as the docs say.
Supertraits do give you a few other things:
trait Super
methods can be called on a dyn Sub
Bound: Sub
implies Bound Sub + Super
impl
s of a trait with the patterntrait Sub: Super { /* ... */ }
impl<T: ?Sized + Super> Sub for T { /* ... */ }
I’m curious: Given that the traits of a struct are set-ish, wouldn’t Rust avoid the diamond issue even if traits had fields (because a struct couldn’t implement the same trait twice)?
Actually I wouldn't say it's inheritance-like. Or at least not really. While the method lookup as well as deref coercion works transitive (Playground), this doesn't extend to trait implementations (Playground).
So Deref
doesn't really establish a supertype/subtype relationship at all.
Moreover, if you attempt to use Deref
as a bound, things get even more ugly (Playground).
This is why I wouldn't say Deref
is inheritance-like. It allows transitive type coercion and method resolution, but a type B
which implements Deref<Target = A>
is not a subtype of A
and cannot generally be used in places where A
is used.
I have been tempted to use Deref
for something inheritance like in past, but I ran into problems sooner or later, so I think the advice in the docs to only use it on smart pointers is justified (though the term "smart pointer" is a bit vague).
Generally, to describe common properties of types, traits should be used to describe those common properties. Unfortunately, when you go that way, you can't really "inherit" easily but you often end up to manually forward some method calls. (Though I think there are crates providing macros to aid you here.)
Regarding the original question: "Super trait, why?", this allows a couple of things:
And maybe there's more. (edit: like @quinedot listed above)
With classic object-oriented inheritance, a Cat is an Animal.
With Deref coercion, it's more like, a String
points to a str
. (Which, by the way, is why there are a lot of redundant implementations in the std
library, e.g. Display
for String
and Display
for str
.)
Don't you mean:
-impl<T: ?Sized + Super> Sub for Super { /* ... */ }
+impl<T: ?Sized + Super> Sub for T { /* ... */ }
Yep, thanks. Fixed.
Yes. The problems with traits that “have fields” is not any diamond issues. The only problem is language design considerations. The precise nature of such a feature would need to be designed, and it would need to have sufficient advantages about currently available alternatives of using methods on the one hand, and sufficiently few disadvantages compared to possible alternative ways of achieving the same advantages.
E.g. there would be the question whether a field needs to be an actual field, or whether a field-like interface could be somehow “simulated” essentially via accessor functions. If the feature is restricted to exposing actual fields, this would be more restrictive than existing alternatives of providing “getter and setter”-style methods, and it would make the level of abstraction that a trait can offer slightly less. As for advantages, there might be the advantage of reducing boilerplate of accessor functions for implementing the trait, though boilerplate can be avoided with macros, too. The usage syntax also would become slightly shorter, though people might dislike that for being less explicit. Borrow-checking could possibly allow accessing multiple fields mutably in parallel, though that only works if different fields must be distinct; would that apply across traits or only within a trait though? Even then, one could also try to design alternative language features that allow methods to expose more fine-grained borrows.
(There might also be existing discussions on “fields in traits” elsewhere that might cover even more points to discuss.)