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

Reply via email to