Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delegation #3108

Closed
lolgeny opened this issue Apr 12, 2021 · 4 comments
Closed

Delegation #3108

lolgeny opened this issue Apr 12, 2021 · 4 comments

Comments

@lolgeny
Copy link

lolgeny commented Apr 12, 2021

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 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:

  • by is 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 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 and as 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!");
	}
}
@shepmaster
Copy link
Member

shepmaster commented Apr 12, 2021

It would be good if you would cross-reference this with any of the previous work done in this area. For example, this RFC on delegation that was closed a few days ago. There's also lots of other related back story.

@lolgeny
Copy link
Author

lolgeny commented Apr 12, 2021

That's a good idea, will do. That link seems to take me to a search of open PRs though, I can't see the PR. Perhaps you gave the wrong one?

@shepmaster
Copy link
Member

Oops: #2393

@jyn514
Copy link
Member

jyn514 commented Jun 3, 2021

Closing per #3133 (comment) since #2393 was postponed.

@jyn514 jyn514 closed this as completed Jun 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants