-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add limited implementation inheritance via traits #9912
Comments
Nominating. |
Let's put it behind a feature flag until it matures, get into the habit of doing so for all new features. |
cc me -- at some point I sketched out a design for this with @pcwalton, not sure if it ever got written up, can't recall, but I'm basically in favor of it. The rough point was to permit structs to extend other structs and to permit traits to extend a struct as well. If one struct S extends another struct T, then S is a substruct of T, and &S is a subtype of &T (as well as ~S <: ~T). S begins with all the fields of T. If a trait extends a struct S, it must be implemented by the struct S or some substruct of S. |
There was some discussion at the 2/26 meeting: https://github.com/mozilla/rust/wiki/Meeting-weekly-2013-02-26 |
What's the plan for virtuals and method resolution? Supposing I have: struct Element : Node { pub fn setAttribute(&mut self) {...} HTMLIframeElement::setAttribute needs to do some special work (checking for 'src' sets) and then forward to Element::setAttribute. So we need two things: (1) HTMLIframeElement's implementation needs a way to explicitly invoke Element's implementation on the same region of memory. (2) If I have some |&Element foo|, invoking foo.setAttribute() needs to invoke the HTMLIframeElement version. Do we need to introduce a |virtual| keyword? |
@bholley the plan is to use traits. The separation between types and impls would be maintained as ever.
Here you can only implement |
Ah, I see. So methods declared in a trait are virtual, and methods declared in a struct are non-virtual. And presumably we can invoke Element::setAttribute from HTMLIframeElement::setAttribute somehow? I assume we're looking only at single inheritance for this stuff? We make extensive use of multiple inheritance in Gecko, but my gut feeling is that we can use entity patterns to solve those uses cases. |
There is no such thing as "methods declared in a struct". Methods are declared in separate And yes, only single inheritance of structs. |
To clarify, using trait methods via the type itself or generics is still just static dispatch. It's only dynamic dispatch via a trait object. |
high priority, no milestone |
I'm working on this (at least starting to) |
\o/ |
@nick29581 good place to draw up a complete RFC :) |
@nikomatsakis plan is to implement the 'obvious' bit - inheritance for structs (no trait/struct mixing, no subtyping), feature gated - then do an RFC for the rest. |
Unfortunately I have a counterproposal. I was thinking about how subtype relationships might be precisely specified, as well as potential subtype relationships between some built-in types, and along the way stumbled into the realization that we might be better served by a plan modelled after GHC's Coercible. MotivationThe first issue is that if you were to have The larger issue is that while we might think we want single inheritance and subtypes, what we actually want is safe zero-cost conversions between binary compatible types, of which subtypes induced by single inheritance are only a smallish subset. It went like this. I was thinking: hmm... wouldn't it be nice if So hopefully well-motivated, here's the plan. InterfaceThe user-facing interface would be exposed as a trait and a function/method:
The trait would be wired-in to the compiler, and user-defined impls of it would be illegal. Where single inheritance and subtyping conflate many different ideas, among them transparent access to superstruct fields, zero-cost conversion from sub- to supertypes, and these conversions being implicit/automatic, There would be another such wired-in trait which I'm going to call
The only reason The most important aspect of the single inheritance proposal is that you could abstract over it, as with traits: traits could specify that they could only be implemented by structs inheriting a given struct, and therefore fields of that struct could be accessed through trait objects without any additional overhead. Here you could accomplish the equivalent by making In terms of surface syntax, we could have a function or method like ImplementationAs with GHC's Coercible (see previous link), these might not actually be implemented by having honest-to-god wired-in impls of them, but it's easier to explain if you pretend that they would. So pretend that things of the following nature would also be wired-in. For any two types
For singleton arrays and their element:
For tuples of a given size with all elements of the same type, and fixed-length arrays of that size and type:
For any struct
For tuples and longer tuples:
For arrays and longer arrays:
The following is for all types and their prefixes, and all generic types with covariant "pointer-like" type parameter contexts. It's what takes you from "
Vice versa for contravariant contexts:
For proper subtypes we can coerce across all covariant contexts, not just pointer-like ones, meaning for instance that we can can coerce a whole array at once (
Again, vice versa for contravariant:
Now some special-cased coercions for mass-borrowing. We can't directly coerce from
Reflexivity:
Transitivity:
Whew! That's all I could think of right now. Unlike GHC, we do not have symmetry in general: a whole lot of conversions are in one direction only. As in GHC, these make-believe impls are wildly overlapping and incoherent, but that doesn't matter, because we don't care which impl is selected (they have no vtable), only whether or not one exists. And also as in GHC, to preserve abstraction boundaries, as a general principle, for those impls which involve conversions between user-defined types, they would only "be in scope" when the means to do the conversion manually are in scope. This means that you could only cast ConclusionI believe this proposal would have many beneficial aspects. By jettisoning those parts of single inheritance and subtyping which are not truly important and concentrating on the ones which are, it would allow greater power and flexibility, while also hopefully allowing a simpler implementation which localizes the logic related to zero-cost conversions to a single part of the language, the built-in |
cc me |
1 similar comment
cc me |
On Tue, Feb 25, 2014 at 03:43:20PM -0800, Gábor Lehel wrote:
This is very interesting. =) A lot to chew on here! I have certainly |
@glaebhoerl: can you give some real life examples for humans (:p) please? I’d like to better understand. |
I never thought of doing this in general (built-in traits can do what they want), but I guess it might be an interesting possibility to think about on a side track. Can you think of any use cases? @sinma: There was some further explication on reddit, but otherwise... what kind of examples would you like to see? |
@glaebhoerl: I found the explanations a bit abstract. :) I think of little examples that shows how «traditionnal» inheritance translates into Rust with this proposal, as well as concrete case where it’s way more practical than traditionnal inheritance (I don’t know Rust well but I know a bit C/C++/Java/Python/etc). I’ll read it again tomorrow I think, and examples helps verify if I understand correctly. :p |
On Wed, Feb 26, 2014 at 03:10:42PM -0800, Gábor Lehel wrote:
Any user-defined trait that represents a property of a type that must |
No subtyping, no interaction with traits. Partially addresses rust-lang#9912.
After some discussion with Lars and Patrick, we think that the motivation for any kind of struct-struct struct-trait inheritance or subtyping is not as pressing as it was. Is there any other use case or motivation for having something like this in the language? Probably looking at something post-1.0 or maybe never. |
It's needed to do COM nicely, and it helps with certain game architectures On Mon, Mar 3, 2014 at 7:00 PM, Nick Cameron notifications@github.comwrote:
|
Could COM use @glaebhoerl's coercions or fixed-position "virtual" (yet free, because the offsets are fixed in the trait definition) fields in traits? |
IMO, Single inheritance works very well in many common,wellknown situations - for a start I beleive its absence makes your AST code harder to navigate. (having it reduces naming& navigation effort and this is a REALLY big deal without an IDE). Many node types would just derive from 'SpannedNode' etc, less having to remember 'which submember is this in' .. and with less reused names between different structs its easier to find the element in question. Given that the feature seems simple i haven't worried about its absence - I know there are many conflicting demands for language features.. but I do worry if you proclaim that it isn't important or its 'never' :( I think it does often work well for game entity layouts, and does work well for UI scene descriptiions aswell. So a programmer familiar with these patterns will find Rust code feels clunky :( It has a very simple low level representation - its possible to do single inheritance easily in ASM by just by reusing slot offsets ... so it makes sense for a low level language to represent it, IMO. You still have the option of using composition where needed - its not like having the feature proclaims that programmers must use inheritance hierarchies; and Rust already has superior ways of doing interfaces so I dont think having it will make Rust programmers suddenly succumb to poor OOP patterns or anything. |
No subtyping, no interaction with traits. Partially addresses rust-lang#9912.
No subtyping, no interaction with traits. Partially addresses rust-lang#9912.
No subtyping, no interaction with traits. Partially addresses #9912.
Hi, a newcomer here, I saw this issue, and thought of chipping in my thoughts.
so basically those methods get generated, and the implementation is just a one liner, forwarding to the inner objects. |
cc me |
So, how would one design Git's object structure in Rust? It would look something like this: struct ObjectId { ... }
struct ObjectHeader { ... }
struct GitObject {
id: ObjectId,
header: ObjectHeader
}
struct Commit /* : GitObject */ { ... }
struct Note /* : GitObject */ { ... }
struct Blob /* : GitObject */ { ... }
struct Tag /* : GitObject */ { ... }
struct Tree /* : GitObject */ { ... } How do the five Git objects inherit Edit: it looks like we might be getting virtual structs. |
@kaisellgren well, you can always do it C like, from the official sources: struct object { ... };
struct commit {
struct object object;
...
}; Thought I'd really like to understand better @glaebhoerl's proposal. |
@jansegre Yes, via composition. That will work, although I was hoping for some nicer solution. |
Hello, newcomer here, I stumbled upon this thread recently. (It's top or one of the top Google results for queries the likes of "rust inheritance".) There is some very interesting reading here, esp. by @nikomatsakis and @glaebhoerl . However, both proposals make me uncomfortable. Both of them are quite complex and it's not immediately obvious what the use case would be like. @nikomatsakis 's proposal breaks the separation between traits and structs, which is something I've always regarded as a great virtue of Rust. Traits extending structs? IMHO it's a concept too reminiscent of @glaebhoerl 's proposal introduces two new 'magical' (ie. compiler-special) traits, which by itself is not necessarily a bad thing, however I don't feel it's justified even by wider applicability. Do we actually need to convert Couldn't this be done in some kind of a KISS (Keep It Simple, Stupid) way? I'm not sure I have enough theoretical foundation to propose a solution myself, so the following might be a complete nonsense, but I'm giving it a try anyway:
How would dynamic dispatch be done? Since traits already provide that and they also already provide possibility for partial implementation (albeit somewhat limited) it would only make sense to use these features for dynamic dispatch in the context of inheritance. The only thing needed would be to allow partial re-implementations of traits that are defined for parent. Basically, when defining No upcasting/downcasting? That's bad. Well, there are pros and cons. For example, if you needed to create a vector of polymorphed objects, it would need to be
Cons:
I'm well aware that this is a very simplistic approach and that I've probably overlooked a thing or twenty, so feel free to criticize anything :) Regardless, whatever approach ends up being featured in the language (if any), I'd be really glad if it:
So, yeah, that'd be my two cents.. |
@vojtechkral I've been playing with Rust for the last couple of weeks and I'm really comfortable with the fact that there is no struct inheritance, it's been a while that I'm less inclined to use traditional OO, I find that most of the time there's better alternative, and that may be why I'm fond of how Rust does it with structs and traits. However for the few cases that a more traditional OO approach actually helps I think it is a very good thing that you pay for it with some syntax, so it won't be what you want to try first and hopefully you'll only use it if you need it. Therefore I'm completely in favor of @glaebhoerl's proposal now that I have some better understanding of it after reading about GHC's Coercible. |
@vojtechkral @jansegre you both may want to read http://discuss.rust-lang.org/t/summary-of-efficient-inheritance-rfcs/494/6 |
@vojtechkral, I think the reason RFC PR 223 has many traits is because it decouples the traditional "components" of inheritance support from each other, giving the programmer maximum flexibility. I believe most people will use sugars (macros) built upon those "low-level" building blocks. The advantage of this approach is that we will not be constrained by any specific flavor of inheritance. This will be handy when interop with other languages (mainly C++, I think) is needed. |
@jansegre Yes, despite the complaints I actually like glaebhoerl's proposal the best out of the others... |
This issue has been moved to the RFCs repo: rust-lang/rfcs#299 |
Not sure if 'implementation inheritance' is the right name for this.
Servo people are complaining a lot about not being able to inherit the memory layout of supertypes, since the DOM is a classic OO hierarchy. Seems like we just have to do it. The basic idea is to let traits specify struct fields.
Needs a complete design, something simple.
The text was updated successfully, but these errors were encountered: