Skip to content
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

Alternative to virtual struct and functions by extending enums #11

Closed
wants to merge 1 commit into from

Conversation

bill-myers
Copy link

This is an alternative plan for virtual structs/functions that implements virtual structs by making it possible to inherit enums to support class inheritance trees, and by automatically dispatching traits over them.

I think this is a superior alternative to both of the current proposals (#5 and #9) and what I originally proposed on the mailing list (which was very similar to #9).

@glaebhoerl
Copy link
Contributor

I haven't been following the virtual struct/function story very closely (and this is a big proposal), could you summarize what the core motivation is / what important things it's adding relative to the current language in terms of expressiveness (as distinct from syntax/ergonomics/convenience/etc)?

@bill-myers
Copy link
Author

It adds an equivalent of Java OO functionality by reusing and extending enums, plus some syntax sugar for impls.

Which basically means having a type (in this RFC, an extended version of enums) such that pointers to this type have these features:

  • Single-word (as opposed to two-word trait objects)
  • Allows direct access to fields (as opposed to calling a function to access them)
  • Can point to any of the multiple run-time subtypes of the type, which are either concrete structs or have the same features of this type
  • The concrete type can be determined given the pointer, including from a self pointer, in constant time
  • There is an expression that executes code, dispatching to different implementations depending on the run-time type of the pointee, implemented via "virtual dispatch" or a faster method
  • Casting up or down does not change the machine representation of the pointer

In other RFCs, the type in question are the new "virtual structs" in #5 or the Fat<T, U> object of #9 where T is a trait that can extend a struct.

The idea of this RFC is that we want to have enums in the language anyway, and we can extend them to perform this role relatively naturally, and that should thus be better than adding something else.

In a nutshell, if you use a vtable pointer as an enum discriminator, and implement match on an enum in the current language by creating a virtual function for each match case, you have a two-level Java-style OO hierarchy where the parent class cannot have fields and enum variants are the subclasses.

This RFC extends the language so that enum have fields, enum variants are first-class structs or enum, multi-level hierarchies are supported, traits can be dispatched over the enum hierarchy, and adds syntax sugar to make it all pleasant.

The motivating issue seems to be to implement the HTML/XML DOM in Servo efficiently, as well as possibly interoperating with COM interfaces without writing the dispatch code by hand.

@nrc
Copy link
Member

nrc commented Mar 29, 2014

I think this is a great idea! I prefer it to virtual structs (RFC 5) since it is feels more Rust-like. I have quite a few suggested tweaks though and I think there are enough it is worth submitting a new RFC rather than commenting here, so I will do that.

The only real question I have with this general approach is how practical it is. Virtual structs have the advantage of being battle tested in the form of 'normal' OO inheritance - we know their advantages and disadvantages. I wonder what if, with this proposal, things get unwieldy when dealing with large numbers of classes with lots of fields and methods and a deep inheritance hierarchy (such as the DOM or COM stuff). I'm not saying it will, just that I don't know and so I have that question.

nrc added a commit to nrc/rfcs that referenced this pull request Mar 30, 2014
Another alternative to RFC rust-lang#5 and an extension/variant of RFC rust-lang#11.

Unify enums and structs by allowing enums to have fields, and structs to have
variants. Allow nested enums/structs. Virtual dispatch of methods on struct/enum
pointers. Remove struct variants. Treat enum variants as first class. Possibly
remove nullary structs and tuple structs.
nrc added a commit to nrc/rfcs that referenced this pull request Mar 30, 2014
Another alternative to RFC rust-lang#5 and an extension/variant of RFC rust-lang#11.

Unify enums and structs by allowing enums to have fields, and structs to have
variants. Allow nested enums/structs. Virtual dispatch of methods on struct/enum
pointers. Remove struct variants. Treat enum variants as first class. Possibly
remove nullary structs and tuple structs.
@nikomatsakis
Copy link
Contributor

Some preliminary thoughts. Sorry for the delay.

  1. I am definitely amenable to making variants be types. It's something we've discussed before. I like the .. notation for indicating an enum can be extended.
  2. You cannot easily combine tuple-like variants with super fields. Syntactically it just makes no sense. How do I instantiate a tuple-like variant in this scenario and specify values for the parent fields, for example? The last time we considered this, we never found a non-goofy syntax. The virtual struct proposal sidestepped this problem by just not permitting tuple structs to extend other structs. I'd personally be inclined to take the same approach here.
  3. Allowing substruct types to have more type parameters than the superstructs is potentially ok, but the interaction with match must be clearly specified. This is also true of the substruct proposal's downcasting rules (which presumably would be based on match).
  4. I do not particularly like mixing traits and virtual fns. I preferred keeping those separated. For one thing, the semantics of your code examples were not entirely clear to me, but also I found it just harder for me to understand when and where vtables would be used, and how the compiler knows what to do in each circumstance. The idea of adding virtual fns directly to types is much easier for me to understand and maps cleanly to my mental model of how method dispatch works (these kinds of virtual fns become "inherent" methods associated with the type, not with a trait). I also find this a cleaner separation in that traits remain largely as pure interfaces one can use to achieve generic programming.
  5. The idea of being able to link to C++ objects with an ABI annotation is very appealing, I don't know how many complexities there are in practice, I expect as long as we keep this to a sane subset of C++ it'll largely work out.

@bill-myers
Copy link
Author

@nikomatsakis

2.You cannot easily combine tuple-like variants with super fields

I suggested this syntax Variant {super_field1: 123}(tuple_arg1, tuple_arg2).

It's not fantastic, but it seems better than banning them (which introduces a special case and a discontinuity - i.e. you want to add a field and suddenly you need to refactor the variant as well).

also I found it just harder for me to understand when and where vtables would be used, and how the compiler knows what to do in each circumstance

Unless an ABI is specified, the compiler is free to arbitrarily choose to use vtables or not, since matching and vtable dispatch are equivalent.

Of course, it's possible to specify this more strictly.

But note that in some cases, like 1 or 2 variants, using an direct call or if statement is going faster, and there is no reason to not do that.

I do not particularly like mixing traits and virtual fns

Here traits are used simply to give a name to sets of methods.

Making them only inherent is possible but isn't great, for many reasons:

  1. You cannot separate different sets of overridable functionality (e.g. input handling vs rendering)
  2. With multi-level hierarchies, it leads to mixing up implementations of abstract methods in parent, in the grandparent, etc. making it more messy.
  3. Requires to add the concept of abstract functions in addition to trait dispatch

Anyway, one could add syntax for it like this:

enum Base
{
   A,
   B
}

impl Base
{
   fn method1();
   fn method2();
}

impl A
{
   fn method1() {...}
   fn method2() {...}
}

impl B
{
   fn method1() {...}
   fn method2() {...}
}

This would be equivalent to putting method1 and method2 each in its own trait, implementing them on Base using "impl as match" and implementing them for A and B.

Problem is, there is no longer any clue in the impls of A and B that you are implementing an abstract interface, and no indication of what the interface is supposed to do other than being Base's interface (and even that requires searching for where method1 and method2 are defined).

These issues may be avoided by having syntax like impl Base for A, or to a more limited extent adding an override fn or override impl; however, that would introduce more complexity and supporting traits would still be necessary.

On the other hand this syntax is less verbose than explicit traits.

I suppose it would be nice to try things out with the DOM and see whether using traits looks nice or excessively verbose.

@zwarich
Copy link

zwarich commented Sep 9, 2014

@bill-myers We are going to be discussing the various proposals for struct inheritance in the weekly Rust meeting soon (hopefully one week from today). We are using the DOM example in https://gist.github.com/jdm/9900569 as a common benchmark for comparing the usability of the different approaches. Can you update this RFC with that example converted to use these new features, or should I attempt it for you?

- Instantiable and overridable structures are separated into struct and enum, rather than having a virtual struct that is both instantiable and overridable
- Only intra-crate inheritance by default, so that it can be better optimized and doesn't try to reinvent traits (virtual structs can also be made inter-crate only, but it is less natural than with enums)
- Overridable methods are declared in traits and not in anonymous impls so that related sets of overridable methods can be separated from each other and have a name and documentation
- It is only possible to override abstract methods and not implemented methods, which makes it harder to create badly designed class hierarchies (but we have syntax sugar to relieve the drawbacks in the form of "impl Trait for Type1, Type2, Type3, ...")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ability to override implemented methods is required for the DOM use case, because of common before/after method idioms in OO code. The overrided method also needs to be able to call the 'superclass' method. Is it even possible to do that with this proposal?

@zwarich
Copy link

zwarich commented Sep 23, 2014

We discussed this in the inheritance meeting and decided that the good ideas from this RFC have been spread amongst the later inheritance RFCs, and those later RFCs satisfy more of the requirements that we are looking to satisfy. Thus it would be best to close this and continue discussion there.

@zwarich zwarich closed this Sep 23, 2014
Centril referenced this pull request in Centril/rfcs Feb 6, 2018
wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants