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
[email protected]
https://mail.mozilla.org/listinfo/rust-dev