Description
TL;DR: Several types in MMTk core use inheritance in OOP. This is not idiomatic in Rust. In the long term, we should switch to a more idiomatic style, such as composition instead of inheritance.
In practice, we should tread lightly, and do refactoring in small steps. Not all such cases need to be eliminated, because there may not always be a better solution.
Examples:
The following types use inheritance.
- Plan: concrete plans extend
CommonPlan
extendsBasePlan
- Space: concrete spaces extend
CommonSpace
- ProcessEdgesWork: Any concrete ProcessEdgesWork always contains a
ProcessEdgesBase
- PageResources: Any concrete page resource contains a
ComonPageResource
Analysis
Inheritance vs composition
An instance of a concrete type contains an instance of an abstract type because the abstract type holds common information shared by multiple concrete types. For example, all spaces should have name.
Conversely, inheritance also means each subclass customises the abstract class in a certain way. For example, all ProcessEdgesWork
have an incoming queue edges to be processed, and an outgoing queue of objects to be scanned, as shown in ProcessEdgesBase
, but each concrete ProcessEdgesWork
customises the way how to process each edge.
If we view the "inheritance" relation as a form of "customisation", we may think that there is only one kind of ProcessEdgesWork, but each instance allow some behaviour to be customised. And there are many different ways of customisation besides OOP-style inheritence. One possibility is composition. If we consider trace_object
as a responsibility of Plan
, then all ProcessEdgesWork are the same. ProcessEdgesWork only calls into Plan to do the actual tracing.
Both #575 and #570 attempt to eliminate plan-specific ProcessEdgesWork implementations. They are examples of replacing inheritance with composition.
Methods for MMTK accidentally added to BasePlan
BasePlan
is special. It comes from the Plan
abstract class in the JikesRVM version of MMTk. Because Java MMTk didn't have the global instance type MMTK
, globally shared data fields are fields of the Plan class.
Most fields in BasePlan
are not really plan-specific. For example,
BasePlan::initialized
indicates if MMTk is initialised. No matter what plan we use, MMTk always needs to be initialised.BasePlan::options
is the options of the MMTK instance set by the command-line or environment variables. They are not plan-specific.BasePlan::gc_requester
is the synchroniser where mutator threads can notify GC threads to start GC. All plans need this.
The only few fields that should conceptually belong to BasePlan
are the three spaces, namely code_space
, code_lo_space
and ro_space
because a plan contains many spaces.
Those shared fields should have been fields of struct MMTK
.
Deref
According to Rust doc: https://doc.rust-lang.org/std/ops/trait.Deref.html
... On the other hand, the rules regarding
Deref
andDerefMut
were designed specifically to accommodate smart pointers. Because of this,Deref
should only be implemented for smart pointers to avoid confusion.
In mmtk-core, the following types implement Deref
and/or DerefMut
.
Group 1:
InitializeOnce<T>
(toT
)
Group 2:
UnsafeOptionsWrapper
(toOptions
)MMTKOption<T>
(toT
)
Group 3:
GenNurseryProcessEdges
(toProcessEdgesBase
)SanityGCProcessEdges
(toProcessEdgesBase
)SFTProcessEdges
(toProcessEdgesBase
)FreeListPageResource
(toCommonFreeListPageResource
)- Type defined by
define_vm_metadata_spec
(toMetadataSpec
)
Group 1, i.e. InitializeOnce<T>
, is a proper use of Deref
, becaue IntializeOnce<T>
is a kind of smart pointer. It is responsible for resource management.
In Group 2, UnsafeOptionsWrapper
and MMTKOption
work around static initialisation. See #541
Group 3 contains types that attempt to implement inheritance of object-oriented programming in Rust. Those belong to the category discussed here.