New RFC for delegation: anyone interested in contributing?

I’m currently considering to write a new (and shorter) RFC for delegation in replacement of 1406 which was postponed.

Threads like this one or this other one showed that a certain number of people may want to contribute. Maybe some of you have already started drafts on their side. Could anyone interested please contact me?

11 Likes

Sounds like an interesting project I’d be up for working on (but not today, too tired for that today…) =)

1 Like

Note to future self: It might be prudent to consider higher kinded types for the future such as:

trait Functor {
    fn fmap<A, B, F: Fn(A) -> B>(self: Self<A>, mapper: F) -> Self<B>;
}

This was already mentioned in the original RFC. Now the fact is blockers were far more “trivial” so while it can of course still be considered as an interesting extension for the future, I’m afraid it is clearly out of reach for the time being.

1 Like

I am definitely in favour of this. I think that for composition to be truly ergonomic we need delegation.

The Custom Self Types RFC could affect the design of delegation.

I’m strongly in favour of this moving ahead and I hope as many people get involved as possible. I have no doubt this will make it into Rust, but whether it happens this year, next year or in 10 years time depends on people volunteering time to make it happen.

Unfortunately, I have negative bandwidth at the moment, but I’ll help as much as I can whenever I get a moment. Thanks, @burakumin for continuing to push on this.

Easy/Ergnonmic delegation is something Rust should definitely have. To me, it is more important than inheritance, and, if we had proper ergonomic delegation, then inheritance wouldn’t be needed as all useful inheritance can effectively be modeled through delegation.

It would be nice to be able to delegate “Properties” as well. Would require some thought.

I have no time this week, but, I’d like to contribute to the development of this RFC.

3 Likes

I’ve created a collaborative doc for us to edit on HackMD. It’s a lighter-weight process than what I proposed previously, which should make it easier to jump in and iterate quickly.

There’s lots of good design discussion in the thread 3 weeks to delegation! Please help. Even if we don’t finish it in time for Rust 2018, it’d be great to figure out if we need to reserve a keyword for the most ergonomic syntax. See: Keywords to reserve for Rust2018 epoch

Please, don’t worry about getting it perfect, just chuck what you’re thinking in the doc. We can always discuss, edit and improve it. :slight_smile:

1 Like

My brain is cooked, need more eyes on this: https://hackmd.io/ZUEHoEgwRF29hbcIyUXIiw?view

Search for “TODO” if you’re not sure where to jump in. Everyone can edit the document from within their web browser, just sign in with GitHub and you’re off to the races!

I’d love to chime in on this, but, I’m too busy this week. Hopefully later this week or next week I’ll be able to put some time into it.

1 Like

Unresolved Questions

  • 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?

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

  • How does delegation interact with specialization? There will be a default impl block in the future. Should we allow delegate to be used in a default impl block?

For the moment, I only have syntactic remarks.

Syntactically, I prefer the previous presentation with use {whoever} for {whatever}, instead of delegate {whatever} to {whatever}. Also, I believe that writing self.field/Self::fun/Self::type instead of field/fun/type is 1/ more consistent with existing syntax; 2/ better suited for future extensions.

On the other hand, I agree that explicitly mentioning const, fn or type is clearer than the previous syntax.

I’ve published an RFC for inherent trait implementation. In the discussion of the previous RFC lang-team seen a close relation to the delegation. While I personally think that delegation and inherent traits have a different scope and can play nicely with each other, “delegating inherent impls” listed as one of the possible future extensions. What is opinion of those who works on delegation? Do you think it’s better to cover inherent traits in a new delegation RFC or do you think we better follow #[inherent] approach described in the RFC2375?

1 Like

This is an important question. Is this an accurate summary of the problem?

A trait BaseTrait is declared in another crate and implemented for SomeType in this crate. Users of this crate must:

  • know the behaviour they need is in BaseTrait in another crate.
  • import BaseTrait everywhere the functionality is needed in SomeType.

Currently this would be solved by:

  • re-export BaseTrait alongside SomeType and glob import at use site; or
  • import BaseTrait in a prelude for this crate, so users only have to import the prelude and their types have full functionality.

So we can focus the design, what’s wrong with or missing from existing solutions?

Please read the issues listed in the RFC for more context. Workarounds still present an inconvenient papercut for crate users, as they have to think about traits structure when they do not care about generics. Plus “just use glob imports almost everywhere” is a bad advice in my opinion.

I think putting {} around the list of items to be delegated would be easier to read:

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

as opposed to:

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

And perhaps allow them to be omitted for * and individual delegates to follow the behaviour of use

2 Likes

I agree a list should be enclosed in brackets. In the name of readability, perhaps reword slightly to permit:

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

I mention this to allow long lists of deferred items without hiding which field will be taking responsibility. That is probably the most important information, so should be highlighted earlier.

The extra context-keyword to seems unsightly though. Alternatives?

  • delegate(field) { fn method };
  • delegate field { fn method };

However this muddles the usage of * to denote all trait items.

4 Likes

Thanks @newpavlov, I haven't read through everything yet. Can you help me precisely define the issues here, so we know exactly what needs solving? I find it helps to focus the design process. e.g.

  • How do these workarounds present an inconvenient papercut for crate users?
  • In what cases do they have to think about traits structure when they do not care about generics?
  • Why is “use glob imports” bad advice? What issues does it cause?

I'd like to make this use case as easy as possible and having a concise problem statement to start with helps time-poor contributors get into it.

What do you think of this, in the Alternatives section:

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

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

The transition to delegation target block syntax isn't quite as nice, but it still works:

delegate fn foo, fn bar {
    &self => self.get_inner(), 
    &mut self => self.get_inner_mut(), 
    self => self.into_inner(), 
    Self <= Obj::from_inner(self) 
}

The argument for this syntax is "when trait items are explicitly listed rather than globbed, the line quickly becomes long and difficult to read." However, this can be solved by moving to field_name; to a new line, e.g.

impl TR for S {
    delegate fn foo, fn bar, fn baz, const MAX, type Item, type Output
        to really_long_field_name;
}

EDIT: We could make this a rule in rustfmt: when not delegating * move to field_name; to a new line. This would make it stand out visually.

In the case of (from the example )

impl TR for S {
    delegate * to f;

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

do we want to have something like “override” in other languages to indicate to the compiler we want to alter “delegation” here explicitly from what we’ve told by delegate * to f; with the semantic to not allow an “override” to elements not defined in f preventing typos etc.?

impl TR for S {
    delegate * to f;

    #[override(from="f")]
    fn foo(&self) -> u32 {
        42
    }
}