New RFC for delegation: anyone interested in contributing?

I’m not sure I understand: since default trait implementations can be provided with custom #[derive(Trait)], what is the advantage of using delegates in that case (delegate *) instead of custom-derive?

@elahn Once the above question has been answered I’d be happy to write up something for the “drawbacks” section, or, if I find the answer particularly enlightening, perhaps I shall withdraw my concern entirely! :smiley:

1 Like

since default trait implementations can be provided with custom #[derive(Trait)], what is the advantage of using delegates in that case (delegate *) instead of custom-derive?

Two things:

  1. You can use "*" for most methods (and etc.) but then have a custom implementation for one or two methods.
  2. It is significantly easier than writing a proc macro for custom-derive
2 Likes

lgtm

1 Like

Thanks.

I had another thought: custom derive wouldn’t really handle the case where struct Foo has two members, bar and baz, for which baz implements one interesting delegatable-trait but bar implements the other.

@elahn I’ve added my concern to the “Drawbacks” section. I rescind the third bullet point in my original comment, since it applies equally to manually writing delegation functions.

1 Like

I sorta took a mental break from this discussion since a) I was a pretty heavy contributor last time so it seemed best not to overdo it, b) I haven't had any new, strong opinions since last time, and c) I was never entirely clear on what was missing to get this to the proper RFC stage. I guess it's no surprise I approve of the current draft (which is Delegation RFC (draft) - HackMD right?) since it appears to be pretty similar to what I wrote, with a ton of new details.

But I can toss around some specific comments:

  • Those "delegate blocks" are... whoa. I'm gonna need a lot of convincing that that's not going way too far for a pure sugar feature. Was there a concrete use case where this is a huge win, or is it just the end result of investigating what it would take to make some of the suggested extensions work?
  • I like the extension to inherent impls. I'm not really understanding @newpavlov's arguments that this is insufficient for the "inherent traits" use cases.
  • I'm tempted to push for self.field instead of field, since self.field seems like it might be forward-compatible with more extensions, as well as forward-compatible with making the self. optional in the future, and because delegate * to 0 for tuple structs is super weird. But this probably depends a lot on how common delegation to non-fields is, so maybe it's not worth further bikeshedding at the RFC stage.
  • The last unresolved question of "How does delegation interact with specialization?" seems like it's already answered by the general principle that this is just sugar for an impl you could write manually. Was there a specific reason I missed why saying "it should just work" doesn't cut it?
  • I think we've hammered out enough detail in the current draft that further discussion is more likely to over-engineer than anything else. If there is some critically broken or underspecified part of this proposal, I won't have a clue what it is until it's posted as a real RFC and the masters of type-fu point it out to us. And the big, elephantine question we do know about has always been which of the many potential extensions are valuable and which aren't, and we're never going to find out until this is in nightly and people start trying it.
1 Like

I skimmed the old threads, and with the benefit of hindsight this seems a lot less mysterious to me now. I completely forgot that the RFC which got postponed never really incorporated any of the feedback and bore almost no resemblance to the strawmen proposals we were throwing around in the old internals thread. In particular, none of the issues in Rfc: delegation of implementation by contactomorph · Pull Request #1406 · rust-lang/rfcs · GitHub apply to our current draft. Within the old internals thread itself, the only strong concern was 3 weeks to delegation! Please help - #33 by withoutboats which I believe is a non-issue with our current draft because arbitrary expressions and static methods are now considered potential extensions.

1 Like

Thanks, @Ixrec. Maybe I got a little paranoid about it stalling or being postponed again. :smiley:

@Yoric, @kaedroho, @JustAPerson and @pythoneer would you like to edit Rationale and alternatives to make sure your preferences/suggestions/concerns are accurately represented?

I’ll give it until end of day tomorrow for people to do any last minute polishing they’ve got planned, then submit the RFC if there are no objections by then.

Thanks, 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!

@elahn or anyone else: Feel free to add my syntax idea to the alternatives. That site doesn’t appear to be working on my machine, I’m unable to add it myself.

I am not saying it insufficient. My point is that it has issues (which I've listed) compared to an alternative approach.

I’ve been thinking about an alternative design that would rely on a “magic” Delegate<Trait, Inner=Type> trait, pretty much like the Iter trait is magic in that it allows for range syntax.

Basically, to delegate in a struct Boss the implementation of a trait Work to a member (or a getter) worker, you would implement the Delegate<Trait> for Boss:

impl Delegate<Work> for Boss {
    type Inner = Worker;
    fn inner(&self) -> &Self::Inner { self.worker }
}

Then, the compiler would automatically implement Work for Delegate<Work, Worker>:

// This would be generated by the compiler
impl<T: Work, U: Delegate<Work, Inner = T>> Work for U {
    // single fn defined in the Work trait, see gist
    fn do_work(&self) -> u64 {
        self.inner().do_work()
    }
}

Here’s some links to a gist and playground showing a more complete implementation (minus the automatic generation by the compiler…).

Here are some pros and cons to this alternative:

  • + No need for added syntax, and the “magic” looks pretty minimal, while still allowing the purpose of “delegate blocks” (transforming the self/&self/&mut self/Self types).
  • + Other functions could accept the std::delegate::Delegate trait, e.g. to perform special treatment on structs that delegate.
  • + Naturally prevents from implementing Delegate<Trait> twice for the same trait (even with different Inner types).
  • + This alternative would naturally extend to inherent impls by implementing a Delegate trait (without a type parameter).
  • - The syntax boilerplate is still significant (see gist). This could be reduced with an autoderive for the Delegate trait and/or smart defaults (blanket impls?)? Or we could add syntax sugar on top of the trait?
  • - This alternative does not address the possibility for “partial delegation” (e.g., override the implementation of some of the trait’s fn, or delegate only some of the trait’s fn). Maybe we could by taking the additional functions defined in the impl of the Delegate trait and putting them in the automatically generated impl of the Work trait for Delegate, but this add more compiler magic.

I’m fairly new to rust, and I’ve only been following this discussion for a few days (in particular I have limited knowledge of the previous RFC that was abandoned), so there might be major caveats (orphan rule?) in this alternative design. I may even have missed the point of the delegation RFC. If so, I apologize in advance for the possible lack of quality or relevance of this contribution.

About the RFC question:

Is the word “delegate” really appropriate in this context?

If I am right, it is wanted to select one portion of source code from one origin and insert it in a destination, after modifying/filtering the namings accordingly under the needs, maintaining the origin-destine code relationship.

This way, when the code in the origin is modified (added or retouched methods, fields, etc), those items/code become propagated to all the destinations whom linked such model for replicate, as if it were done by hand, what means is the lexer engine who would play here, before sending to compiler stage.

If this is correct, I think that propagate or replicate could be alternative words for this matter. Depending of were is going to be placed Origin and Destine in the syntax:

 // Example.1 , modification of stile proposed in RFC 

 impl Foo for Bar {
     replicate Foo { * | field_des }
 }
 // The redundancy of signalizing Foo as Origin is helpful for newcomers
 // , just it is needed to say it can be omitted.

But, reading the comments of the threads talking about this matter, the content of {...}, with the cross of time (may be years), it could become a:

 // Example 2, refactoring matrix

 //      What          From                To
 //  ------------   -----------        -----------
 { 
     fn    *      { fieldA_ori         | fieldA_des         }
     fn func_name { fieldB_ori         | fieldB_des         }
     field        { fieldC_ori         | fieldC_des         }
     name fn      { func_name_ori      | func_name_des      }
     type         { typeA_name         | typeB_name         }
     use for      { self.0  typeA_name | self.0  typeB_name }

     fn func_name2, fn func_name3
     {
         fieldB_ori  | fieldB_des,
         fieldC_ori  | fieldC_des,
         fieldD_ori  | fieldD_des,
     }
     etc
 }

What I think would be very hard to follow, newbies and no so newbies, from my humble point of view. Or probably I did not understood this part of the RFC.

But, thinking on it, one thing the code text editors could do after detect the delegation is to show the replicated code/functions in place ( with an unfold button ), for quick understanding of whats being replicated in example 2. In fact, If that happens, probably I would not find hard difficulties in the example 3, what I would consider in such case almost an advantage, because I would see in real time the pattern changes. Or perhaps is to much dependence or guessing in thirds ?

I am just starting to explore the language, so I apologize in advance for the lack of quality in my comments.

Thank you all for being there fighting

The RFC has been posted.

To be honest, I’m not sure how far it brings us if we only have the ability to delegate associated methods (as opposed to entire impls). To me, it feels like 95% of the time I spend writing delegated impls is spent on getting all of the generic type parameters and where bounds and associated types correct, whereas the methods can usually be copied and pasted between each impl for a trait without any modification beyond a search and replace for the delegated field name.

6 Likes

Re: advanced cases, I think that for sufficiently advanced or complex cases one should just write out the function, to an extent. Perhaps complex stuff like delegating to an enum might be better left to using a macro or writing it out yourself manually. That being said, simpler stuff like delegating to an expression (e.g self.inner.lock()) or something would be nice to have a syntax for. In regards to that, it would be nice if any expression would be able to be put there (such as a block) to allow arbitrary levels of getting, locking, cloning, atomic loads, or whatever the user needs to do.

It might also be worth it to look at how Kotlin does delegation (https://kotlinlang.org/docs/reference/delegation.html).

A thought not important enough for the RFC thread: should delegate fn * to ... also be supported? (And const * and type * and…)

@elahn I'm very deceived about how things turned about the subject. When I contacted you months ago you showed no signs of life. I have no issues with that. We all have periods when we have other priorities. That was precisely my case these days but I had already started a new RFC. That was the reason I invited interested people to contact me in the original post.

And when I come back I remark you created an independent RFC within 3 weeks. Now of course the fact I was the author of the original RFC does not give me any ownership on this feature but maybe we could have joined our efforts. I have only skimmed the new RFC but when considering the reason that prevented the original RFC to be accepted I really doubt that this new one has better chance. Maybe my position will change when I have a better view.

@cramertj's comment was not what blocked the RFC. Look at his next message. In the end I think he was confident that the RFC could have be accepted with some clarification.

@withoutboats has always been the one opposing the acceptation and as far as I can say what he wanted was:

  • a really narrower focus to start with (notably: delegation to fields only)
  • a detailed discussion on the validity of delegation depending on the type of self.

I still have to read the new RFC with great care but it does not seem to me that any of these conditions are met.

Hey @burakumin, I’m sorry you feel that way. I did try to contact you and looked for an RFC draft to contribute to. Since there appeared to be no activity, I created a collaborative RFC draft on HackMD which the community has worked on the last few weeks and continues to work on throughout the RFC process; I am only one contributor.

My only interest is in having the best possible initial and forward compatible design that answers all necessary questions for the RFC to be accepted, so we can all reap the benefits of delegation in Rust. I hope you will join forces with us and help make this a reality.

Just because the RFC has been posted, doesn’t mean it’s finalised. Please add to, modify and make it better. It would be great if everyone following the RFC PR would modify the draft to address suggestions, questions and concerns, with a discussion here when necessary.

1 Like

Hey all, I’m in a health crash and could really use some help addressing the questions raised in the RFC PR thread. Any volunteers?