Pre-RFC: Delegation
Many times, it would be useful if we could delegate in rust - the delegation pattern is good and there are many other uses of it (like in the newtype idiom). Currently, all we can do is implement Deref
. But this has several disadvantages:
- If we wanted to actually implement
Deref
for what it's designed for, we can't - We have to inherit all of the methods
- We can only do it once This is more like extending another class than delegation. So, here's the proposed syntax.
trait Foo {
fn bar(&self);
}
struct Delegate;
impl Foo for Delegate {
fn bar(&self) {
println!("Hello, world!");
}
}
struct Derived {
delegate: Delegate
}
impl Foo by self.delegate for Derived {}
Some notes on this syntax:
I've chosen to useas
, because it's the most fitting keyword already in the language, and adding another keyword (likeby
orfrom
) will break things.as
is unclear, so a weak keywordby
can be used instead- As we can see, the delegate is a member of the struct, not another class. This allows things like dynamic delegation and modifying the delegate (imagine a
Counter
trait and struct, you'd need to update the counter) - Instead of a trait, we can also specify
*
to copy all methods. - Maybe the
for
andas
should swap? - Note the curly brackets, as we see another use of this syntax with more control:
trait Foo {
fn bar(&self);
fn quux(&self);
}
struct Delegate;
impl Foo for Delegate {
fn bar(&self) {
println!("Hello, world!");
}
fn quux(&self) {
println!("Hello, quux!");
}
}
struct Derived {
delegate: Delegate
}
impl Foo for Derived
where bar by self.delegate {
fn quux(&self) {
println!("Not delegated");
}
}
This where
clause is similar to that of a generic parameter list in that it just allows for more control/less ugliness.
Here's the exact syntax (EBNF):
DelegateImpl = "impl", (trait | "*") (as_clause | for_clause, where_clause);
trait = ? ident ?;
as_clause = "by", ? expression ?, for_clause;
where_clause = "where", function_name, "by", expression, ",", [where_clause];
for_clause = "for", struct;
struct = ? ident ?;
function_name = ? ident ?;
Motivations
Composition
Currently, there is no struct inheritance in rust - this is a good thing, as you usually favour composition over inheritance. But you still have to refer to the composed object - the methods don't compose. Now you could do:
struct Base;
impl Base {
fn foo(&self) {
println!("Base class");
}
}
struct Derived {
base: Base
}
impl * by self.base for Base {}
fn main() {
let myDerived = Derived {base: Base};
myDerived.foo();
}
Newtype
The newtype idiom is very common as we can do various things to a type - implementing a trait or just wrapping around to make a new type (Days
and Months
aren't the same even if they are both i64
). But, like in composition, you still have to call .0
or implement Deref
, neither of which you should have to do.
struct Base;
impl Base {
println!("Base");
}
struct Days(Base);
impl * by Base for Days {}
Delegation pattern
If it looks like a duck and acts like a duck, it is a duck.
This is the basis of the delegation pattern: using public traits to specify your type, then having a private implementation of it and a public creator function. This way, people can easily modify your type and pass their own version without affecting the rest of the code, which doesn't care. We could do:
pub trait Duck {
fn quack(&self);
fn create_duck() -> Duck {
DuckImpl
}
}
struct DuckImpl;
impl Duck for DuckImpl {
fn quack(&self) {
println!("Quack.");
}
}
/// `DivingDuck` is a duck and more!
struct DivingDuck;
impl Duck by DuckImpl for DivingDuck {}
impl DivingDuck {
pub fn swim(&self) {
println!("Dive!");
}
}