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
Addtrait forCentimeters. - Itās easy to grok (I think). Just smashing types together!
What I donāt like
- Iām not satisfied with the
compose_implssyntax. - As I said, implementations for traditional āabstract classesā donāt feel quite right. You could write a
IElementimplementation for a tuple type that has aElementfield, then callset_attributeon 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
implcase. We could forbid multiple components of the same type, and use a new keyword likecomposefor 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!ā