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

RFC: Delegation #2393

Closed
wants to merge 6 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 111 additions & 23 deletions text/0000-delegation.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,20 @@ rust-lang/rust | 845 |
rust-lang/cargo | 38 |
servo/servo | 314 |

By providing syntax sugar for the composition pattern, it can remain/become a privileged tool for code reuse while being as terse as the inheritance-based equivalent. It could also enable ergonomic implementation of custom widgets in a pure Rust GUI library.
Functional programmers like @Centril love delegation and absolutely adore that they can do the following in Haskell with `{-# GeneralizedNewtypeDeriving #-}`:

```haskell
newtype NormT m (a :: *) = NormT { _runNormT :: WriterT Unique m a }
deriving ( Eq, Ord, Show, Read, Generic, Typeable
, Functor, Applicative, Monad, MonadFix, MonadIO, MonadZip
, Alternative, MonadPlus, MonadTrans, MFunctor, MMonad
, MonadError e, MonadState s, MonadReader r
, MonadWriter Unique )
```

This is massive code reuse and not in any OOP language ^,-
Copy link
Contributor

@Centril Centril Apr 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o.O you copied in this verbatim =D


By providing syntax sugar for the composition pattern, it becomes a privileged tool for code reuse while being as terse as the inheritance-based equivalent. It could also enable ergonomic implementation of custom widgets in a pure Rust GUI library.

Related discussions:

Expand All @@ -76,15 +89,15 @@ https://internals.rust-lang.org/t/3-weeks-to-delegation-please-help/5742

In Rust, we prefer composition over inheritence for code reuse. For common cases, we make this convenient with delegation syntax sugar.

Whenever you have a struct S with a member field `f` of type F and F already implements a trait TR, you can delegate the implementation of TR for S to `f` using the contextual keyword `delegate`:
Whenever you have a struct `S` with a member field `f` of type `F` and `F` already implements a trait `TR`, you can delegate the implementation of `TR` for `S` to `f` using the keyword `delegate`:

```rust
impl TR for S {
delegate * to f;
}
```

This is pure sugar, and does exactly the same thing as if you “manually delegated” all the items of TR like this:
This is pure sugar, and does exactly the same thing as if you “manually delegated” all the items of `TR` like this:

```rust
impl TR for S {
Expand Down Expand Up @@ -112,16 +125,15 @@ impl TR for S {
42
}
}

```

Aside from the implementation of foo(), this has exactly the same meaning as the first example.
Aside from the implementation of `foo()`, this has exactly the same meaning as the first example.

If you only want to delegate specific items, rather than “all” or “most” items, then replace `*` with a comma-separated list of only the items you want to delegate. Since it’s possible for types and functions to have the same name, the items must be prefixed with `fn`, `const` and `type` as appropriate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good default here could be that fn is assumed (since it is most common), and so you could instead write:

impl TR for S {
    delegate foo, bar, const MAX, type Item 
        to f;
}

another possible shorthand notation could be (but I am not proposing it at this point):

impl TR for S {
    delegate
        fn { foo, bar, the_hulk, black_widdow },
        const { MAX, MIX },
        type { Item, Baz, Bar }
        to f;
}

but you are allowed to prefix with fn if you so wish.


```rust
impl TR for S {
delegate fn foo, fn bar, const MAX, type Item
delegate fn foo, fn bar, const MAX, type Item
to f;
}
```
Expand Down Expand Up @@ -178,19 +190,19 @@ A delegation statement can only appear inside a trait impl block. Delegation ins

Delegation must be to a field on `Self`. Other kinds of implementer expressions are left as future extensions. This also means delegation can only be done on structs for now.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I think it is good to start off being conservative.


Delegation statements must be the first items in an impl block. There may be more than one delegation statement, but they must all be at the top.
There may be more than one delegation statement. For readability, `rustfmt` moves delegation statements to the top of an impl block.
Copy link
Member

@scottmcm scottmcm Apr 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps justify why this is more readable? I can see an argument for *, but especially for single-method delegation, I'm not convinced. Particularly in a case where I'm changing existing code to replace manual delegation with delegate, I'd expect rustfmt to leave the the item where it is so the diff is the obvious one. And even in new code, I might be intentionally matching the order of the methods on the trait, for example. (Nit-picky: if it's going to move multiple, it would need to pick an order to put them in.)

Copy link
Contributor

@Centril Centril Apr 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My current thinking here is that:

  • The argument starts from *, which should be at the top so that it is seen first
  • For consistency with *, you place all delegation where delegate * to whatever would be

An argument could however be made that all delegations should be at the bottom since they often will be delegate * and so they are a sort of "catch the rest", i.e, they function like match x { important => .. , _ => .. } does wrt. _ =>.

With respect to order, I'd first group items by item type and then in each group alphabetically so:

  • const
  • type
  • fn

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, does rustfmt reorder any other items? I tried on play and it doesn't seem to reorder type and fn in an impl block, for example...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting!

PS: we could leave formatting bikeshed up to a style-fmt RFC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


A delegation statement always consists of:

- the contextual keyword `delegate`
- the keyword `delegate`
- either a `*`, or a comma-separated list of items being delegated
- the contextual keyword `to`
- the delegation target `field_name`
- a semicolon

An “item being delegated” is always two tokens. The first token must be either `fn`, `type` or `const`. The second is any valid identifier for a trait item.

The semantics of a delegation statement should be the same as if the programmer had written each delegated item implementation manually. For instance, if the trait TR has a default implementation for method foo(), and the type F does not provide its own implementation, then delegating TR to F means using TR’s implementation of foo(). If F does provide its own implementation, then delegating TR to F means using F’s implementation of foo(). The only additional power granted by this feature is that `delegate *` can automatically change what items get implemented if the underlying trait TR and type F get changed accordingly. There can be at most one `delegate *` per `impl` block.
The semantics of a delegation statement should be the same as if the programmer had written each delegated item implementation manually. For instance, if the trait `TR` has a default implementation for method `foo()`, and the type `F` does not provide its own implementation, then delegating `TR` to `F` means using `TR`’s implementation of `foo()`. If `F` does provide its own implementation, then delegating `TR` to `F` means using `F`’s implementation of `foo()`. The only additional power granted by this feature is that `delegate *` can automatically change what items get implemented if the underlying trait `TR` and type `F` get changed accordingly. There can be at most one `delegate *` per `impl` block.

To generate the wrapper function:

Expand Down Expand Up @@ -245,7 +257,7 @@ impl<T> AppendOnlyVec<T> {
}
```

This is an example of a "restricted type" without using a trait. We can easily delegate the methods we want to the inner Vec, thereby restricting access to functionality we don't want to expose.
This is an example of a "restricted type" without using a trait. We can easily delegate the methods we want to the inner `Vec`, thereby restricting access to functionality we don't want to expose.


## Inherent Traits
Expand Down Expand Up @@ -286,7 +298,7 @@ delegate fn foo, fn bar {
}
```

This extension requires `Delegate` to be a keyword in edition 2018 to avoid parser complexity, although it is not necessarily expression context, so we could potentially make it contextual. If this is accepted, it would make sense to make `delegate` a keyword as well. If this extension is not ruled out during the RFC process, these keywords should be reserved in edition 2018.
In addition to `delegate`, this extension requires `Delegate` to be a keyword in edition 2018 to avoid parser complexity, although it is not necessarily expression context, so we could potentially make it contextual. If this extension is not ruled out during the RFC process, `Delegate` should also be reserved in edition 2018. Alternatively, this could be written as `x: Rc<delegate>`, breaking tradition with capitalized `Self`.


A delegate block could potentially be used to implement some [other extensions](#other-extensions):
Expand Down Expand Up @@ -433,17 +445,43 @@ impl Write for Wrapper {
```


## `unimplemented!()`
[unimplemented]: #unimplemented

This particular expression could be allowed as a special case as opposed to allowing arbitrary expressions.

```rust
impl TR for S {
delegate const MAX, type Item
to f;
delegate _ to unimplemented!();

fn foo(&self) -> u32 {
42
}
}
```

Unspecified `fn`s are stubbed out with `unimplemented!()` to allow rapid prototyping. This would give most of the benefits of `#[unfinished]` in [RFC #2205](https://github.com/rust-lang/rfcs/pull/2205) without introducing a new attribute.

```rust
fn bar(&self, x: u32, y: u32, z: u32) -> u32 {
unimplemented!()
}
```


## Other Extensions
[other_extensions]: #other_extensions

- Delegating to static values or free functions.
- Delegating to arbitrary expressions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one particular expression could be really good for prototyping speed here: unimplemented!() for fns. This would give you most of the benefits of #[unfinished] without introducing a new attribute. #2205

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like something worth adding to the huge pile of potential future extensions.

- Delegating a trait impl to an inherent impl.
- Delegating a method foo() to a differently-named method bar() that happens to have the same signature.
- Delegating a method `foo()` to a differently-named method `bar()` that happens to have the same signature.
- Delegating “multiple Self arguments” for traits like PartialOrd, so that `delegate * to f;` would desugar to something like `self.f.partial_cmp(other.f)`
- Delegating for an enum where every variant's data type implements the same trait.
- Delegating trait fields, once that feature is implemented.
- Delegating multiple traits in a single statement, e.g.
- Delegating multiple traits in a single item, e.g.
```rust
impl PartialEq + PartialOrd + Ord for PackageId {
delegate * to f;
Expand All @@ -454,8 +492,12 @@ impl Write for Wrapper {
# Drawbacks
[drawbacks]: #drawbacks

- Yet another (contextual) keyword proposal.
- This is basically a new way of writing trait implementations, which is something we can already do. Having two ways of doing the same thing is often undesirable.
- A new keyword `delegate` in edition 2018, in accordance with the lang team [keyword policy](https://paper.dropbox.com/doc/Keyword-policy-SmIMziXBzoQOEQmRgjJPm) that new features should be real keywords for maintenance reasons. Here is a quick review of the breakage risk:
- TL;DR: The risk is quite minimal and something we could probably live with.
- Usage as ident in libstd: No
- Usage as the name of a crate: No
- Usage as idents in crates ([sourcegraph](https://sourcegraph.com/search?q=repogroup:crates+case:yes++%5Cb%28%28let%7Cconst%7Ctype%7C%29%5Cs%2Bdelegate%5Cs%2B%3D%7C%28fn%7Cimpl%7Cmod%7Cstruct%7Cenum%7Cunion%7Ctrait%29%5Cs%2Bdelegate%29%5Cb+max:400)): 19+ uses
- This is a new way of writing trait implementations and we already have two ways, including `#[derive(..)]`.
- If too many of the future extensions are implemented, this could become an overly complex feature.
- The `delegate *` syntax may be too implicit:
- When a function is delegated implicitly, it is harder for a reader to find the actual definition, especially if multiple struct members providing different traits are delegated to using the `*` syntax.
Expand Down Expand Up @@ -522,12 +564,15 @@ impl TR for S {
}
```

This makes it more obvious the target is a struct field and is certainly more clear for tuple structs. This would be a good choice if we want to allow custom implementer expressions without a delegate block, when there is no variance due to the type of self.
This makes it more obvious the target is a struct field and is certainly more clear for tuple structs.

This would be a good choice if we want to allow custom implementer expressions without a delegate block, when there is no variance due to the type of `self`. However, we could probably enclose the expression within `{ expr }` as is done with const generics to disambiguate, so this is a weak objection.


`self.` is unnecessary and could lead newcomers to believe they can write arbitrary Rust code there. However, this risk also applies to allowing getter methods in the basic delegation statement.

This unresolved question is relevant to the decision:
- Are there any cases of [Custom Self Types](https://github.com/rust-lang/rfcs/pull/2362) where self needs to be manually dereferenced, e.g.
- Are there any cases of [Custom Self Types](https://github.com/rust-lang/rfcs/pull/2362) where `self` needs to be manually dereferenced, e.g.
```rust
impl TR for S {
fn foo(self: Arc<Self>) -> u32 {
Expand Down Expand Up @@ -567,6 +612,50 @@ impl S { delegate * to trait TR; }

However, in the case of delegating most of a trait's methods, an `impl` block is still required and now the "overridden" method can be seperated from the delegate statement, which could be confusing and easier to miss what is happening.

We could offer both syntaxes as a good 4 step ratchet:

1. first try `#[derive(..)]`
2. then go with `delegate TR::* to S::f;`
3. then delegate inside an impl
4. finally implement things manually.


### Assume items are `fn`

When listing items to delegate, the prefix `fn` is assumed, since it is most common. Items may be prefixed with `fn` if desired.

```rust
impl TR for S {
delegate foo, bar, const MAX, type Item
to f;
}
impl TR for S {
delegate to f for foo, bar, const MAX, type Item;
}
delegate TR::{foo, bar, const MAX, type Item}
to S::f;
delegate push to AppendOnlyVec::0;
```


### Different syntax for delegating most trait items

```rust
impl TR for S {
delegate _ to f;

fn foo(&self) -> u32 {
42
}
}
```

This overcomes the drawback: the `delegate *` syntax may be too implicit.

It also overcomes the objection to omitting the `impl` block: the "overridden" method can be seperated from the delegate statement, which could be confusing and easier to miss what is happening.

The increase in cognitive load is minimal, since `_` is widely used to mean "inferred by the compiler."


### Other syntax options the authors chose not to use in this RFC:

Expand All @@ -581,19 +670,16 @@ Many of these syntaxes were never “rejected” in the original RFC’s comment

### What is the impact of not doing this?

Without a mechanism for efficient code reuse, Rust will continue to be criticised as "verbose" and "requiring a lot of boilerplate." Delegation isn't a perfect vaccine for that criticism, but goes a long way by making code reuse as easy in Rust as in common OOP languages.
Without a mechanism for efficient code reuse, Rust will continue to be criticised as "verbose" and "requiring a lot of boilerplate." Delegation isn't a perfect vaccine for that criticism, but goes a long way by making code reuse as easy in Rust as in common OOP and functional languages.


# Unresolved Questions
[unresolved_questions]: #unresolved_questions

We expect to resolve through the RFC process before this gets merged:

- Is it useful and/or feasible to allow delegation statements to appear anywhere in the impl block, rather than all at the top?
- Is “contextual keyword” the right term and mechanism for `delegate` and `to` as proposed here?
- Do we want to support all kinds of trait items, or should we be even more minimalist and support only methods in the first iteration?
- Although the syntax and desugaring for "delegating some methods to one field and some to another" is straightforward, should it be postponed as a possible future extension?
- Are there any cases of [Custom Self Types](https://github.com/rust-lang/rfcs/pull/2362) where self needs to be manually dereferenced, e.g.
- Are there any cases of [Custom Self Types](https://github.com/rust-lang/rfcs/pull/2362) where `self` needs to be manually dereferenced, e.g.
```rust
impl TR for S {
fn foo(self: Arc<Self>) -> u32 {
Expand All @@ -603,10 +689,12 @@ We expect to resolve through the RFC process before this gets merged:
```
If so, can these be handled during implementation of this feature or is upfront design work required?
- There is a concern about _inherent traits_ causing duplicated symbols, can this be resolved during implementation?
- Is the possible future extension _delegate block_ ruled out? If not, keywords `delegate`/`Delegate` should be reserved in edition 2018.
- For the possible future extension _delegate block_, should we reserve the keyword `Delegate` in edition 2018?
- Should `to` be a keyword in edition 2018?
- Should we implement the proposed syntax or one of the alternatives in nightly? We may wish to gain experience using a particlular syntax on nightly before committing to it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be done concurrently with the RFC if someone has the time, but I don't think an experimental RFC is necessary here, the current design is pretty good.

- Are there any possible extensions the proposed syntax is not forward compatible with?

We expect to resolve through the implementation of this feature before stabilization:

- How does delegation interact with specialization? There will be a [default impl](https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#default-impls) block in the future. Should we allow `delegate` to be used in a `default impl` block?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would the reason to not allow them be?

- The authors do not have a specific reason to disallow this and are listing the question as it was raised during discussion and they do not know enough about specialization to answer it.