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

RFC: Delegation #2393

wants to merge 6 commits into from

Conversation

elahn
Copy link

@elahn elahn commented Apr 6, 2018

Syntax sugar for efficient code reuse via the composition pattern. Wrapper functions are generated for a struct, delegating most or all of a trait’s impl block to a member field that already implements the trait.

Rendered

Please Note:

This RFC is a group effort from the Rust community. Whenever an issue is raised, please edit the RFC draft to address it as best you can.

If the design needs to be bikeshedded, please do so on this internals thread.

Whenever an issue or question has been resolved, please submit a PR to this RFC.


Thank you, everyone for your contributions, they’ve been a big help. If we continue this collaborative style throughout the RFC process, I’ve no doubt we can address any concerns that arise and get this puppy accepted!

@Centril Centril added the T-lang label Apr 6, 2018
Copy link
Contributor

@Centril Centril left a comment

💯🥇 to all collaborators on the heroic effort of writing and collaborating on this RFC!

This RFC seems generally well done; I have some formatting nits here and there and some other "more substantive" (not nits) concerns.


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`:
Copy link
Contributor

@Centril Centril Apr 6, 2018

Choose a reason for hiding this comment

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

Formatting nits: Should probably use backticks on S, F, TR.


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`:
Copy link
Contributor

@Centril Centril Apr 6, 2018

Choose a reason for hiding this comment

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

We probably want to make delegate a real keyword due to current (one day old) lang team keyword policy 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): 19+ uses

}
```

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

@Centril Centril Apr 6, 2018

Choose a reason for hiding this comment

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

Formatting nit: backticks on TR

```

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

@Centril Centril Apr 6, 2018

Choose a reason for hiding this comment

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

Formatting nit: backticks on foo()


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

@Centril Centril Apr 6, 2018

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.


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?
Copy link
Contributor

@Centril Centril Apr 6, 2018

Choose a reason for hiding this comment

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

My gut feeling is that it is feasible. delegate <stuff> to <field>; should be considered an item, and as such, you'd [rough sketch] first do a pass collecting all the items and after that you simply extract the delegate items and desugar those into a flat set, remove all the idents (LHS) found in the remaining set from the flat set, and then you finally merge the sets.

I think rustfmt should put them at the top, but people should be able to do as they like and the grammar should be flexible enough to accommodate that if it is technically possible.


- 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?
Copy link
Contributor

@Centril Centril Apr 6, 2018

Choose a reason for hiding this comment

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

I think the initial set is conservative enough 👍

- 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.
Copy link
Contributor

@Centril Centril Apr 6, 2018

Choose a reason for hiding this comment

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

Formatting nit: backticks on self.

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

@Centril Centril Apr 6, 2018

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.


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

@Centril Centril Apr 6, 2018

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?

@elahn
Copy link
Author

@elahn elahn commented Apr 6, 2018

In the previous revision:

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.

Unresolved question: Is it useful and/or feasible to allow delegation statements to appear anywhere in the impl block, rather than all at the top?

@Centril: My gut feeling is that it is feasible. delegate to ; should be considered an item, and as such, you'd [rough sketch] first do a pass collecting all the items and after that you simply extract the delegate items and desugar those into a flat set, remove all the idents (LHS) found in the remaining set from the flat set, and then you finally merge the sets.

I think rustfmt should put them at the top, but people should be able to do as they like and the grammar should be flexible enough to accommodate that if it is technically possible.

@Ixrec: Based on my memory of past and current delegation threads:

  1. Some people thought this was required to make the contextual keyword option viable. I think this line of reasoning predates epochs/editions. It's obviously obsolete now that we have official guidance on new keyword introduction.
  2. When an impl block contains a delegation as well as a regular item implementation, it seems better style to put the delegation on top. Especially when it's a * delegation, since "Foo implements trait T by delegating almost everything to field f, except that method foo is ... " is the natural way to describe that sort of thing.

So imo, as part of editing this to say delegate should be a real keyword, we should also delete this restriction. Putting the delegate item(s) at the top is probably still good style, but there's no need to make it mandatory.

Unresolved question: 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?

@Centril: I think the initial set is conservative enough 👍

These questions are considered resolved unless someone objects.

impl AddAssign for AddOnlyCounter {
delegate * to 0;
}
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.

I was already thinking of suggesting that it be delegate <list> to self.<field>, and this solidified the feeling. (delegate fn hash to 0 looks like it'd do 0.hash() to me.) I like there being restrictions as a starting point, but I wouldn't be surprised at all for this to get expanded, and expanding to expressions (perhaps a subset thereof, but note that unimplemented!() would just work if it took anything) seems like the most obvious one, so I think the syntax should at least look like a normal expression from the beginning. (Readers: feel free to 👍(👎) if you would(n't) like me to make a PR for that.)

Edit: Oh, I see the mention of this in the alternatives. I'm not convinced learnability is a big problem; even with just a field name it seems like it's "oh, they'll put self. in front" and expect more things to work, and it'd be easy to give a very clear error for "delegation only supports a single field". More abstractly, is there a reason it couldn't support an arbitrary expression? There's already a "implementer expression" concept in the reference section below. (Well, ones where let _ = expr; could infer the type, giving the same "cannot infer type for T" errors you get from things like that let if you tried to do something like delegate * to self.0.into();.)

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.

Personally, I'm quite torn on the subject of self vs. not. While self.field is clearer, it is also a bit more verbose; and you could support arbitrary expressions with a { } enclosing of { unimplemented!() }.
With respect to error messages and learnability I agree that this isn't a big concern.


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.

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

@scottmcm scottmcm Apr 8, 2018

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

@Centril Centril Apr 8, 2018

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

@scottmcm scottmcm Apr 8, 2018

Choose a reason for hiding this comment

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

@scottmcm
Copy link
Member

@scottmcm scottmcm commented Apr 7, 2018

What happens when things go wrong? For example, what happens if I try to

impl Default for AddOnlyCounter {
    delegate * to 0;
}

Is there a way to map output (and output types)? For example, if I do

impl Add for AddOnlyCounter {
    delegate * to 0;
}

It seems like the output type is most likely u32, but I'd probably want it to be AddOnlyCounter. And what's the RHS? Does it just fail because the impl wants the RHS to be AddOnlyCounter, but self.0.add wants a u32?

In general, it seems like Self anywhere that's not also self is a landmine here.

```rust
fn check_name(&self, name: &str, ignore_capitals: bool, state: &mut State) -> bool {
self.f.check_name(&name, ignore_capitals, &mut state)
}
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.

I don't think I know what "according to their type" means here. Why are the extra &s needed, instead of just moving them all? Perhaps self.f.check_name({name}, {ignore_capitals}, {state}).

, 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

@petrochenkov
Copy link
Contributor

@petrochenkov petrochenkov commented Apr 7, 2018

Yes, please (ignoring specific syntax).
This has potential to remove more useless boilerplate than all the "ergonomic initiative" RFCs combined.

impl TR for S {
delegate * to field_one;
delegate fn foo, const MAX, type Item
to field_two;
Copy link

@tanriol tanriol Apr 7, 2018

Choose a reason for hiding this comment

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

Would be nice to have a bit more motivation for this. Treating a trait as a unit of behavior, in what situations does it make sense to delegate some behavior to one field and some to a different one?


Many of these syntaxes were never “rejected” in the original RFC’s comment thread and are likely still on the table. This list merely describes the authors' rationale for preferring `delegate ... to field_name;` over all of these alternatives.

- `impl TR for S use self.F { ... }` was criticized in the first RFC’s comment thread for looking too much like inheritance.

Choose a reason for hiding this comment

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

Why is that a problem? It basically is inheritance. Or a restricted version of it, because inheritance delegates all methods to the designated member called “base” while this only delegates methods of specific trait. But that restriction is obvious from this syntax.

Copy link
Contributor

@Ixrec Ixrec Apr 7, 2018

Choose a reason for hiding this comment

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

"Inheritance" traditionally means a LOT more than simply delegating implementations to another type. For instance: committing to the same interface, having the same internal structure as another type, or being implicitly convertable to other types. In Rust those things are usually kept separate, which we'd like to keep doing.

So if we wanted to make this part of the RFC clearer, we could say that impl TR for S use self.F {} makes it surprising that a field named F must already exist in S's declaration, rather than being implicitly added by this impl TR for S use self.F {}. Or we could say that impl TR for S use self.F {} makes it surprising that S cannot be implicitly converted to an F. And so on.

I don't personally think there's any one or two specific features in the "inheritance" grabbag that most users would mistakently expect when they see impl TR for S use self.F {}, but I do think a lot of people would mistakenly expect something on top of method delegation if they saw that syntax. It might be that "it looks too much like inheritance" is the only concise way to present that argument without getting into the weeds of what people think "inheritance" means.

Choose a reason for hiding this comment

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

Inheritance does not mean all that much more than delegation, really. It does mean that all interfaces will be delegated, but it should be obvious it's not happening here, because it starts by stating which trait it is delegating. It does not mean committing to the same structure, only to containing that structure, but that needs to happen here as well. The only other thing inheritance does is the implicit coercion of reference. That won't happen here, but we only said we are delegating specific trait after all. And coercion to that trait does work.

This form would not be appropriate for delegating inherent methods, because that would look like it might be doing more than it does. But for the traits, it would be convenient shortcut that does not really look like promising more than it does.

makes it surprising that a field named F must already exist in S's declaration

Since it does not mention the type of F, it's quite clear that that still has to be given.

makes it surprising that S cannot be implicitly converted to an F

I would somewhat agree for inherent methods, i.e. impl S use self.F {}. But I wouldn't fear that much for the impl TR for S case.

Choose a reason for hiding this comment

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

Hm, but there is one difference that I have to admit would be quite confusing—in case of inheritance, the overridden behaviour applies even if you've got a reference to the “base field”. But in our case it won't. In fact, I fear delegating some methods, but not all of them, will be confusing with any syntax.

@Diggsey
Copy link
Contributor

@Diggsey Diggsey commented Apr 8, 2018

It's possible I missed it, but there's no mention of delegating to nested fields, eg. delegate * to x.y - it seems... odd to constrain it to a direct field.

@WiSaGaN
Copy link
Contributor

@WiSaGaN WiSaGaN commented Apr 8, 2018

fn method = self.field.method; This syntax was suggested for delegating a single item. It’s not clear how to extend it to delegating multiple items, “most” items or all items in an impl.

I still prefer this syntax proposed by @eddyb . This syntax does not introduce new keyword (contextual or not). And the meaning is obvious, and "feels" just right. I would argue that we should not encourage "glob" delegations. I am wondering how many cases are left for "glob" if we can use impl MyTrait for MyType => self.0; to delegate all methods in a trait.

```rust
impl TR for S {
fn foo(self: Box<Self>) -> u32 {
self.f.foo()
Copy link
Member

@Nemo157 Nemo157 Apr 8, 2018

Choose a reason for hiding this comment

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

I don’t believe the linked Custom Self Types RFC adds support for implicit projections of smart pointers (I don’t think it is possible in general, given an Rc<T> you can’t obtain an Rc<SomeFieldOfT>), which would be necessary for this to work.

@Ixrec
Copy link
Contributor

@Ixrec Ixrec commented Apr 8, 2018

@WiSaGaN

I would argue that we should not encourage "glob" delegations.

Do you mean "glob delegation" as in any syntax to delegate everything not explicitly implemented, or just the use of * in the proposed syntax? I would've thought "glob delegation" meant the former, but you appear to be proposing that impl MyTrait for MyType => self.0; do exactly the same thing in the very next sentence.

@WiSaGaN
Copy link
Contributor

@WiSaGaN WiSaGaN commented Apr 8, 2018

@Ixrec the RFC states the shortcoming of fn method = self.field.method; syntax is the inability to handle multiple, most or all items in an impl. I assume this means the cases not covered by impl MyTrait for MyType => self.0;. In these cases, explicitly spelling out the functions one need instead of conveniently pulling in all functions not grouped by any traits seems desirable because it

  • promotes conservatively exposing functions in the new type that reduces the possibility of accidentlly pulling in unintended functions
  • minimizes reader/maintainer's search effort for what are delegated exactly

@leoyvens
Copy link

@leoyvens leoyvens commented Apr 8, 2018

I'm happy that there are people working on this as I find the motivation compelling. But one of the reasons #1406 was closed was that it tried to do too much, with too much syntax. I think this RFC still has that same problem. Personally the only feature I want is to delegate an entire trait implementation to a field. An attribute would do fine, I see no need for first-class syntax.

@Ixrec
Copy link
Contributor

@Ixrec Ixrec commented Apr 8, 2018

@leodasvacas To clarify, are you only talking about the introduction of dedicated syntax, or do you also feel this RFC "does too much"? It sounds like you're saying both, but the actual functionality proposed here (i.e., ignoring all of the possible future extensions) is a small subset of what #1406 proposed and under-specified. And part of the reason #1406 was closed is because its proposed reuse of an existing keyword was potentially confusing and ambiguous; now that we have epochs/editions introducing a new keyword is intended to be an improvement on it (if you have a specific alternative to a new keyword without the problems of #1406 I'd love to hear it).

@WiSaGaN I honestly can't figure out what you're trying to say in that comment. Are you objecting to the feature of delegating all items? Or the feature of delegating most/all non-explicitly implemented items? Or some specific syntax for either or both of those? I believe impl MyTrait for MyType => self.0; syntax can handle the all case but not the most case, while I don't see how fn method = self.field.method; could handle either.

@WiSaGaN
Copy link
Contributor

@WiSaGaN WiSaGaN commented Apr 9, 2018

@Ixrec We may be able to use impl MyTrait for MyType => self.0; with fn method = self.field.method;. These two won't cover the case where a type has many inherent methods, and you want to delegate all of them in one line, and the "most" cases. By using fn method = self.field.method; in these cases, you typed more, but it has the two benefits listed in my previous comment.
This approach though, does not introduce "delegate", nor it introduces the "*".

@clarfonthey
Copy link
Contributor

@clarfonthey clarfonthey commented Apr 10, 2018

One other argument in favour of using self.field instead of field is this:

impl<'a, T: 'a + Trait> Trait for &'a T {
    delegate * to *self;
}

Which, IMHO, should be allowed from the start. So many traits do this and I think it's extremely reasonable to include it. Additionally, disallowing delegate * to self at least when deny(unconditional_recursion) is on is also super important; personally, I'd even say that this should be a hard error.

I think that it should be clarified that this syntax should not alter the behaviour compared to the manual equivalent. To the point where, if they differ, it should be considered a compiler bug. This goes either way; if something works with delegation (as it should) when it fails manually, I'd consider it a bug.

@kornelski
Copy link
Contributor

@kornelski kornelski commented Apr 10, 2018

Could I delegate Ord to a combination of two fields?

struct MyStruct {
   field_a_ignore_me: Foo,
   field_b: u8,
   field_c: String,
}

impl Ord for MyStruct {
    delegate * to |&self| (self.field_b, self.field_c);
}

@Ixrec
Copy link
Contributor

@Ixrec Ixrec commented Apr 10, 2018

@kornelski No. That probably falls under this item on the list of possible future extensions:

Delegating “multiple Self arguments” for traits like PartialOrd, so that delegate * to f; would desugar to something like self.f.partial_cmp(other.f)

@Havvy
Copy link
Contributor

@Havvy Havvy commented Apr 11, 2018

Nit: This is proposing a delegate associated item, not a statement.

@elahn
Copy link
Author

@elahn elahn commented Apr 12, 2018

@scottmcm: What happens when things go wrong? For example, what happens if I try to

impl Default for AddOnlyCounter {
    delegate * to 0;
}

A nice compiler error explaining Default isn't implemented for u32, so the implementation for items of Default cannot be delegated to AddOnlyCounter::0.

Is there a way to map output (and output types)? For example, if I do

impl Add for AddOnlyCounter {
    delegate * to 0;
}

It seems like the output type is most likely u32, but I'd probably want it to be AddOnlyCounter. And what's the RHS? Does it just fail because the impl wants the RHS to be AddOnlyCounter, but self.0.add wants a u32?

Since we're delegating to u32, the signature of u32.add() is used, so RHS is u32:

impl Add for AddOnlyCounter {
    type Output = <u32 as Add>::Output;

    fn add(self, other: u32) -> u32 { self.0.add(other) }
}

To map the output type using the possible future extension "delegate block":

impl Add for AddOnlyCounter {
    type Output = AddOnlyCounter;
    delegate * {
        |self| self.0
    } -> {
        |delegate| AddOnlyCounter { delegate }
    }
}

// Generated code:
impl Add for AddOnlyCounter {
    type Output = AddOnlyCounter;

    fn add(self, other: u32) -> AddOnlyCounter {
        AddOnlyCounter { self.0.add(other) }
    }
}

For RHS to be AddOnlyCounter, the possible future extension "delegating multiple Self arguments" would be needed.

@mehcode
Copy link

@mehcode mehcode commented Apr 16, 2018

I think I'm missing the point here. Couldn't this be trivially provided by a crate and a proc macro for the common case? Does it really need to be syntax?

#[delegates(Trait, "bar")]
struct Foo { bar: Bar }

@clarfonthey
Copy link
Contributor

@clarfonthey clarfonthey commented Apr 17, 2018

@mehcode the proc macro would have to have the ability to tell what methods Trait has, so, it'd be extremely limited.

@scottmcm
Copy link
Member

@scottmcm scottmcm commented Apr 19, 2018

Meta-comment: I definitely want some form of delegation, but I think this RFC could still do a better job of carving out a simple and uncontroversial subset. For example, when I think of delegating Add for struct MyNewtype(u32), I expect MyNewtype: Add<MyNewtype, Output=MyNewtype>. This RFC doesn't do that. And that's fine; I'd just rather, then, that it was a "isn't supported for delegation at this time" error. Maybe a simple rule can be found for "delegation-safe" items (like there's a rule for "dyn-compatible traits) that this can be restricted to for now, with additional support added later for the tricky cases.

First Draft: Only methods that don't mention Self other than in self, &self, and &mut self.

A nice compiler error explaining Default isn't implemented for u32

Umm, u32 is Default: https://doc.rust-lang.org/beta/std/primitive.u32.html#impl-Default

@Kixunil
Copy link

@Kixunil Kixunil commented Nov 26, 2020

@hobofan hard to tell over text but it seems to me you had a bad day or something. That's fine, I'm not offended/affected. Just want to let you know I definitely didn't want to discount any work and I didn't call the RFC bikeshedding, but the discussion that proposes tons of various syntaxes. My idea is to get something working in nightly to see how it behaves in real life as that might actually help indecision if people are allowed to try it out.

Thanks for pointing out the issue with await. Is there a way we can fine-tune it? Perhaps giving people louder heads-up?

Could you explain what exactly is not simple about such forwarding? My thinking is if we could write a hypothetical macro that is guaranteed to be non-recursive, expanded last and can take definition of any item just by specifying the path of this item, then it could be done with such a macro.

I didn't bother to go through all the standard library traits and including them in some built-in way

And this is exactly the problem I'm talking about. Even you, the creator of crate supposed to address the problem, were not motivated to do the work. Such work is tedious and uninteresting and could be done by a machine - what this RFC is all about.

@hobofan
Copy link

@hobofan hobofan commented Nov 26, 2020

@Kixunil

My idea is to get something working in nightly to see how it behaves in real life as that might actually help indecision if people are allowed to try it out.

Thanks for pointing out the issue with await. Is there a way we can fine-tune it?

The best way to avoid similar situations is to try out as much as possible in the compiler already. If someone wants to implement a prototype of this feature directly in the compiler for people here to try out and evaluate against different approaches, they are already free to do so right now, and that has been done for RFCs with similar syntactic concerns in the past. There is no need for an RFC to be accepted for someone to start doing so and on the flip side an implementation won't just start materializing in nightly by accepting an RFC unless there is someone motivated to implement the feature anyways.

Could you explain what exactly is not simple about such forwarding?

Of course the underlying mechanism is "just" forwarding, but I think the RFC already lays out all the non-simple parts about it (e.g. partial forwarding). That paired with arriving at a good syntax makes this feature non-trivial in my opinion. If one is already is set on the syntax as laid out in the RFC, it might indeed be straightforward to implement (I don't know enough about the compiler internals to judge that).

Even you, the creator of crate supposed to address the problem, were not motivated to do the work.

I'm in no way "supposed" to do that work. I wrote and published the crate mostly to show that previous limitations of the macro system can finally be worked around and to serve my use-case at the time that wasn't handled well by existing crates (delegating large traits through multiple levels). If there is a pressing problem to a user of the crate they are free to contribute that or even just open an issue for that. (In particular for your example of delegating std:: traits on newtype structs, I've always been satisfied with derive_more).

@Kixunil
Copy link

@Kixunil Kixunil commented Nov 26, 2020

There is no need for an RFC to be accepted for someone to start doing so

Oh, I had the impression that RFC is required. Thanks for clarifying!

@ghost

This comment has been minimized.

@Kixunil
Copy link

@Kixunil Kixunil commented Jan 14, 2021

@hhggit could you also provide an example of case when there is an inherent fn and trait fn with the same name and when there are two or more trait fns with the same name?

@ibraheemdev
Copy link
Contributor

@ibraheemdev ibraheemdev commented Jan 14, 2021

I started a thread proposing a variation of "delegation" called "forwarding" (Pre-RFC: Forwarding) which is closer to the syntax suggested by @eddyb and @WiSaGaN. This is a very highly requested feature and that consensus seems to be that it makes sense for it to be included in the language. Hopefully we can decide on a path forward soon!

@ibraheemdev
Copy link
Contributor

@ibraheemdev ibraheemdev commented Mar 17, 2021

I've been thinking about this issue more lately, and I've come to the decision that the design outlined by this RFC is probably the best way to go about it. It outlines a much simpler and straightforward solution to the problem than any other proposals I have seen (#493, #2431), although some of those proposals cover more use cases and should not be discounted. This issue comes up a lot from beginners. You explain to them that Rust favors composition over inheritance, and then they get confused as to why there is so much boilerplate. Someone wants to implement an external trait for an external type, and you tell them to new-type it, but then they lose all the other trait implementations and have to resort to copy pasting self.0.foo() everywhere, which again, is very confusing. For a language that argues against inheritance, it feels very odd that there is no solution to efficient code re-use. External crates like delegate cover some use cases, but delegating an external trait implementation is still only possible manually, or with hacks like ambassador's copy-paste solution, and again, it is confusing that Rust favors composition over inheritance, but you often still need an external library (both of which depend on syn - compile times) to get rid of boilerplate. Many jump to implementing Deref, which was not it's original purpose, and yet is recommended by the Rust book. Can we move forward with this RFC or get some triage from the lang team? I feel that this is a very important issue, but the discussion around it has died down. Rust 2021 edition is coming up soon, and this RFC introduces a new keyword (delegate), so it seems like a good time to get things started up again.

@Kixunil
Copy link

@Kixunil Kixunil commented Mar 18, 2021

Wanted to help this move so I gave it another read. I agree that this proposal is very good. I'm just not sure about * delegating everything including types, while explicit syntax doesn't allow delegating types. I'm not convinced forbidding types at first is beneficial. I suspect the difficulty of implementing it is marginal.

@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Mar 30, 2021

@rfcbot fcp postpone

Hello everyone; we discussed this RFC in our backlog bonanza. The consensus was that we that we should postpone it, as we don't think we have the bandwidth to see to it right now, but we are very interested in this general direction, and we agree that this addresses some real problems in Rust.

We would like to encourage folks to discuss it when the time comes for us to discuss our upcoming roadmap (one of the procedural changes we have in mind is to make it clearer when we'd be open to bigger proposals).

Speaking for the team, I do want to apologize. I realize that we encouraged @elahn and others to do this design work, and then let it languish and I'm sorry about that. :(

@rfcbot
Copy link

@rfcbot rfcbot commented Mar 30, 2021

Team member @nikomatsakis has proposed to postpone this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period disposition-postpone final-comment-period labels Mar 30, 2021
@rfcbot
Copy link

@rfcbot rfcbot commented Mar 30, 2021

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period label Mar 30, 2021
@nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Mar 30, 2021

I've checked the names of those folks who were present in the meeting.

@rfcbot rfcbot added finished-final-comment-period and removed final-comment-period labels Apr 9, 2021
@rfcbot
Copy link

@rfcbot rfcbot commented Apr 9, 2021

The final comment period, with a disposition to postpone, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

The RFC is now postponed.

@rfcbot rfcbot added to-announce postponed and removed disposition-postpone labels Apr 9, 2021
@rfcbot rfcbot closed this Apr 9, 2021
@shepmaster shepmaster mentioned this pull request Apr 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-delegation A-impls A-syntax A-traits finished-final-comment-period postponed T-lang to-announce
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet