What-up. Late to the party
Iāve been slowly (almost against my will) hatching an idea for the efficient code reuse challenge. Iāve tried to contain the urge to share it, but itās an itch I have to scratch! It works in my brain, but my brain might be broken, so Iām coming here for feedback. If you guys sort of like what I have so far, Iād be more than happy to write a detailed RFC over the next few days.
This is a ābriefā description of what I have in mind. I wonāt talk about how this interacts with non-struct types (such as primitives and closures), how this deals with pub
and non-pub fields, the performance implications, or even how casting works, but I would in an RFC (and I will through discussion).
The basic idea is to rip off multiple inheritance, add more customization, then relabel it as ātype compositionā (or whatever name you guys like). Hereās the rundown:
- Tuple structs are the mounting point for the new functionality.
- Optionally ācomposeā the tuple struct to inherit all, or hand chosen trait implementations, methods, and fields.
- Optionally map fields to component typesā fields.
- Allow structs in type bounds for generics. This would require that the type have all of the fields from the struct, and the fields must have the same name and type.
Hereās a little taste of the DOM example:
trait IElement {
fn before_set_attr(&self, key: &String, value: &String);
fn after_set_attr(&self, key: &String, value: &String);
}
struct ElementData {
attrs: Vec<String, String>,
};
impl INode for ElementData {
fn as_element<'a>(&'a self) -> Option<&'a Element> {
Some(self)
}
}
#[compose_traits]
#[compose_impls]
#[compose_fields]
struct Element(ElementData, Node)
impl<T> T
where T: IElement,
T: Element,
{
fn set_attribute(&self, key: &String, value: &String) {
self.before_set_attr(key, value);
//...update attrs...
self.after_set_attr(key, value);
}
}
Note that in the example, Node
is also a compound type (albeit a very simple one). You can compose compound types together to create a hierarchy a la traditional inheritance.
When you look at traditional inheritance, each class in the hierarchy has its virtual methods, its methods, and its member fields. In Rust, this is all nicely separated for us. Traits provide a means for virtual methods, impl
blocks provide a means for methods, and structs include their fields.
Looking at the DOM example, there are 3 compiler attributes above the compound Element
, corresponding to the virtual methods, methods, and fields. By default, the compose attributes will inherit everything. You can also explicitly choose what to inherit:
#[compose_traits(INode)]
#[compose_impls(
some_method1: ElementData::some_method,
some_method2: Node::some_method,
)]
#[compose_fields(NodeData{parent: some_field_name})]
Note: Regarding compose_fields
, all data is always part of the new compound. Inheriting a field means that a field is created on the compound that maps to a field in a component struct. Inheriting a field simply provides ergonomic access.
The compose_traits
attribute takes traits that you want to inherit. By default, when a trait method is called on a compound, the respective implementation for each component is called. For methods that return a value, the value of the āchild-mostā component is used. The āchild-mostā component is the earliest component in the tuple struct parameters to implement the trait. You can also explicitly implement a trait for a compound type:
impl INode for Element {
fn as_element<'a>(&'a self) -> &'a Element {
Node::as_element(self); // Useless hurray
ElementData::as_element(self)
}
}
compose_impls
takes specific methods, and allows you to rename them in the compound type. You can also list a type if you want all of its methods.
compose_fields
uses the destructuring syntax to map component fields to their new names in compound type.
Finally, weāll talk about the portion which Iām most unsure about: implementing abstract classes. Technically, in traditional OOP, Element
from the DOM example is an abstract class; it canāt be allocated. It has a method, set_attribute
, that makes use of virtual methods not implemented yet (before_set_attr
and after_set_attr
). To handle abstract classes, my current idea is to allow type bounds to allow requiring a component. Essentially, a component is just a tuple field. This has some weird implications, and I hope thereās a better way.
impl<T> T
where T: IElement,
T: Element,
{
fn set_attribute(&self, key: &String, value: &String) {
self.before_set_attr(key, value);
//...update attrs...
self.after_set_attr(key, value);
}
}
The impl
header looks awfully weird for something thatās simply trying to say āimplement for types that have Element as a component and implement IElementā. I need to think about how this will work more.
What I like
- This keeps everything separate. Traits donāt get to have fields like they do in another RFC.
- This makes the ānewtype patternā more ergonomic. For example, you can inherit the
Add
trait forCentimeters
. - Itās easy to grok (I think). Just smashing types together!
What I donāt like
- Iām not satisfied with the
compose_impls
syntax. - As I said, implementations for traditional āabstract classesā donāt feel quite right. You could write a
IElement
implementation for a tuple type that has aElement
field, then callset_attribute
on it. Funky business. - Iām not satisfied with the way trait methods that return a value is handled. Using the āchild-mostā componentās return type just feels hacky.
- In theory, you could compose a type with multiple components of the same type. This doesnāt really make sense, and is hard to handle in the abstract class
impl
case. We could forbid multiple components of the same type, and use a new keyword likecompose
for type composition so that you canāt compose tuple structs. I donāt like this very much either. - The struct as generic type bound concept in general is not well thought out on my end at all. Need to think on it more.
What do you guys think?
It definitely needs a lot of refining, but what do you guys think about the overall concept? Should I write an RFC, or is the idea unworkable?
If you think it is workable, and have suggestions for any of the issues I mentioned, please share!
Hope Iām not oversteppingā¦ āWhoās this guy? Hasnāt made a single commit to Rust? Proposes weird new features? Hah!ā