Sorry if something similar has already been proposed.
In short
A nice feature of usual OO languages is the implicit code reuse through inheritance: the fact a subclass does not need to redeclare any method existing in its base class provided the behaviour is the one expected. Wouldn’t it be a nice feature if Rust had some syntaxic sugar to mimic this ?
Composition and inheritance
When I have:
// java-like language
interface Foo {
void doSomething();
}
class Bar : Foo {
void doSomething() { ... }
}
The nice thing is that in class Baz : Bar {}
method doSomething()
is implicitely available as a method of Baz. A well known limitation of this pattern and one of the main arguments for the composition over inheritance principle is that it cannot be extended to import methods from different classes (unless of course your language offers multiple inheritance). That’s why the compositional equivalent is often presented as a better practice:
class Baz : Foo {
Bar bar;
void doSomething() { this.bar.doSomething(); }
}
Rust has no type inheritance (not yet?) but composition can of course be used in a similar manner:
trait Foo {
fn do_something(&self);
}
impl Foo for Bar {
fn do_something(&self) { ... }
}
struct Baz {
...
bar : Bar
}
impl Foo for Baz {
fn do_something(&self) {
bar.do_something();
}
}
Now the main drawback for this pattern in Rust and in most OO languages is the boilerplate implied. Basically you need to write obvious code that just delegates each method call to a given field again and again. This is not a big deal if your interface/trait is short. This starts to be annoying when it gets longer.
Automatic delegation for implementations
What if we had some nice syntactic sugar to do this automatically?
impl Foo for Baz use self.bar {}
One nice feature is that when Self is used for other parameters it can also benefit from the sugar:
trait Foo {
fn do_something(&self);
fn do_something_else(&self, other: &Self);
}
impl Foo for Baz use self.bar {
// also implicitely defines
// fn do_something_else(&self, other: &Self) {
// bar.do_something_else(other.bar);
// }
}
We can even imagine using a general expression instead of a single field provided the type of the expression implements the trait:
fn get_bar(x: i8, y: u16, foo: &Foo) -> Bar { ... }
impl Foo for Baz use get_bar(self.x, self.y, self) {
// fn do_something_else(&self, other: &Self) {
// get_bar(x, y, self).do_something_else(get_bar(other.x, other.y, other));
// }
}
One of the potential issues is that it may not work very well with the move semantics. However it’s easy to combine implementation delegation and actual implementation so one can write the methods that cannot or must not be generated automatically. Everything missing is left for delegation.
Possible extensions
- This could benefit from a potential “combined implementation”:
trait Qux {
fn do_something_complex(&self);
fn do_something_fast(&self);
}
...
impl Foo + Qux for Baz use self.bar
{
fn do_something_fast(&self) {
// implement with this one explicitely
}
// the 3 remaining methods are delegated to self.bar
}
- If we can imagine delegation for method parameters, we can of course think about something similar for the method result.
trait Foo {
fn new(name: &str) -> Self;
fn do_something(&self) { ... }
fn do_something_else(&self, other: &Self);
}
impl Foo for Baz use self.bar, Baz { bar: return, x: 4, y : 0 } {
// implicitely defines
// fn new(name: &str) -> Self {
// Baz { bar: new(name), x: 4, y : 0 }
// }
}
Advantages / Disadvantages
-
As in usual OO any addition of new methods on a trait only implies new method implementations at the “super type” level. All delegating implementations are unchanged.
-
This is equivalent to code reuse through multiple inheritance with limited amount of code. Now this implies no inheritance relation at alI: it is only about code reuse NOT about type substitution/inheritance.
-
This might be considered as too implicit when Rust tries generally to be explicit.
-
This might not be so useful if the vast majority of traits contain only one method.