Bill's posting pleases me more than any other I have seen so far.
Virtual functions in C++ have been an especially fruitful source of unfortunate consequences, both in the language and in users' programs. It is common in C++ coding guidelines to forbid public virtual functions, to help avoid mixing implementation details into a public interface, but that only mitigates one problem. Virtual functions have been a source of confusion, too, because they can be overloaded two different ways, with identical syntax but radically different semantics. The compiler cannot help catch mistakes because either might reasonably have been intended. This is part of the rationale for "pure virtual" classes, just so that failing to override a virtual as intended through some trivial error stands some chance of being noticed. For this reason, some coding guidelines go farther and _only_ allow overriding a parent's pure virtual functions. Virtual functions are the chief ingredient in what Alex Stepanov calls "O-O gook". It should surprise no one that Java and C# went the wrong way by making all member functions virtual, thereby exposing all programs and all programmers to these ills all the time. That said, virtual functions do provide a more structured form of function pointer, which we do need. Any such feature should start with the problems it must solve, and work toward a defensible design, not by patching traditional O-O method overriding. Rust has the advantage over early C++ that lambdas and macros are available as well-defined building blocks. Ideally, Rust's architectural replacement for virtual functions would be purely a standard-library construct, demonstrating greater expressiveness to enable user code to do what another language is obliged to have built into its core. [aside: I don't know of any family connection to Bill.] Nathan Myers On 03/11/2014 12:09 PM, Bill Myers wrote:
I see a proposal to add "virtual struct" and "virtual fn" in the workweek meeting notes, which appears to add an exact copy of Java's OO system to Rust. I think however that this should be carefully considered, and preferably not added at all (or failing that, feature gated and discouraged). The core problem of "virtual functions" (shared by Java's classes, etc.) is that rather than exposing a single public API, they expose two: the API formed by public functions, and the API formed by virtual functions to be overridden by subclasses, and the second API is exposed in an inflexible and unclean way. A much better way of allowing to override part of a struct's behavior is by defining a trait with the overridable functionality, and allowing to pass in an implementation of the trait to the base class, while also providing a default implementation if desired. Another way is to have the "subclass" implement all the traits that the "base class" implements, include a field of the "base class" type, and then direct all non-overridden functionality to the "base class" (here syntax sugar can be easily added to eliminate the boilerplate, by automatically implementing all non-implemented trait functions by calling the same function on the base class field). These approaches can be combined, as the first approach allows to change the "inside" behavior of the base class, while the second one allows to put extra behavior "around" the base class code. The fact that OO using virtual functions (as opposed to traits) is a bad design is one of the crucial steps forward of the design of languages like Go and current Rust compared to earlier OO languages, and Rust should not go backwards on this. Here is a list of issues with virtual functions: 1. Incentive for bad documentation Usually there is no documentation for how virtual functions are supposed to be overridden, and it as awkward to add it since it needs to be mixed with the documentation on how to use the struct 2. Mishmashing multiple unrelated APIs With traits, you could pass in multiple objects to implement separate sets of overridable functionality; with virtual structs you need to mishmash all those interfaces into a single set of virtual functions, all sharing data even when not appropriate. 3. No encapsulation Private data for virtual function implementations is accessible to all other functions in the struct. This means for instance that if you have a virtual function called "compute_foo()" that is implemented by default by reading a "foo" field in the base class, then all other parts of the base class can access "foo" too. If anything else accesses mistakenly "foo" directly, which it can, then overriding "compute_foo()" will not work as expected. If compute_foo() were provided by an external trait implementation, then "foo" would be private and inaccessible, eliminating the problem. 4. Data for overridden implementations left there in a "zombie" state. In the above example, if you override "compute_foo()", the foo variable in the base class will no longer be used, yet it will still be present in the type and allocated in memory. 5. Inability to statically dispatch With a trait implementation, you can pass the concrete type as a generic parameter, allowing static dispatch. If you instead call an overridable virtual function, then you can't dispatch that statically at all (unless you add cumbersome syntax for that). 6. Adds a ton of unnecessary complexity to the language _______________________________________________ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
_______________________________________________ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev