-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
This is a sort of half-RFC draft that I may write into a full RFC if it's a good idea.
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
Dereffor 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:
byis a weak keyword- 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
Countertrait and struct, you'd need to update the counter) - Instead of a trait, we can also specify
*to copy all methods. - Maybe the
forandasshould 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!");
}
}