3 weeks to delegation! Please help

It's used extensively in Objective C / Swift. The thing is that the delegate (n. /ˈdɛlɪɡət/) is the value/type to which you delegate (v. /ˈdɛlɪˌɡeɪt/) functionality. C# has /ˈdɛlɪɡət/s, while I believe this proposal is about /ˈdɛlɪˌɡeɪt/ing.

2 Likes

100% agree. This was definitely my intent in my earlier posts.

Part of the reason I opted to have an explicit * in my proposal rather than just delegate to self.f; was because I felt an "explicit *" helped carry exactly these sorts of connotations. I believe it already has a sort of "everything you need, not necessarily everything that's available" connotation from glob imports. Maybe I should've mentioned this earlier.

2 Likes

That makes sense and I'm with you on that perspective and I see it as intutive because * would be "pretty silly" (technical term :P) if it did anything but what you're proposing.

Perhaps "very semantically different" was the wrong phrase. What I mean is simply that, as an author and a reader of code, impl means "definitions will be provided here," while delegate means "aliases will be provided here," and knowing in advance which I am looking at would, I think, simplify the reading process, and possibly the syntax. You are correct that there would be no difference in client code or calling convention, except possibly as an optimization if the trait implementation is known at compile time (i.e. no runtime dispatch) and the alias is available during inlining; in this case, presumably, the alias target could be invoked directly.

So my intent in using the word "semantic" was precisely to indicate a difference that doesn't really matter in its de-sugared form, but may matter to the programmers working on the project.

That said, I don't feel particularly strongly about this. Primarily, I wanted aliasing to go in its own block because I think it just looks a little cleaner and won't require a change to the existing impl block syntax.

No, I do not have an alternate syntax in mind. I would prefer something that explicitly shows either the included members or the excluded members, a la this Bash-ism (I know, I know, it's hideous):

delegate TR for S {
    [* !(fn foo, fn bar)] = S.f.*;
}

... or even yet another contextual keyword:

delegate TR for S {
    [* except fn foo, fn bar] = S.f.*;
}

....but I don't have a strong rationale for this, I suppose, and I guess it's okay if [*] means something like Ruby's method_missing.

For static methods, I'm actually not sure what a delegate would be supposed do, assuming f is something like an instance variable.

That makes sense. I could live with delegate to self.f for * except fn foo;, though I still think delegation should happen inside an otherwise-normal impl block so it might look redundant to say "except fn foo" only one line above your "fn foo" definition.

I have no idea whether this is a real use case, but I was imagining it'd do this:

trait MagicNumber {
    fn num() -> i32;
}

struct A {}

impl MagicNumber for A {
    fn num() -> i32 {
        42
    }
}

struct B {
    a: A
}

impl MagicNumber for A {
    delegate to self.a for *;

    // desugars to...

    fn num() -> i32 {
        A::num()
    }
}

Actually writing that out made me really question whether I want the self in the delegation statement anymore...

For static methods, I really don’t think self should be included:

impl MagicNumber for A {
    delegate to B for *;
}

…because this is exactly what you’re actually doing: some static methods of A are identical to corresponding static methods of B, so they’re aliased. No instances are involved at any point.

I mean, you could go all the way to require the type always:

delegate to B at self.b for *;

You’d only need an at clause when there are methods. The challenge with doing

delegate to B ... 

and

delegate to self.b ...

is that you have a context that accepts either a value or a type, when these are parsed differently.

This also opens he door to

delegate to B at Default::default() for *;
1 Like

I agree, for the following reasons:

  1. Having a separate block for delegation seems counterproductive to me. I don't think it looks any cleaner to do delegation in a separate block and it feels like yet another subdivision of where I have to look to get a sense of where each function comes from and what it is.

    (In fact, if you're only delegating one thing or only impling one thing, then it adds at least two extra lines of clutter for no benefit at all.)

    EDIT: Actually, now that I think about it, doing delegation in a separate block sort of reminds of C++'s approach to distinguishing between public, protected, or private member declarations... for what it's worth.

  2. I don't want to have to repeat myself, needlessly keeping two lists of what I've implemented in sync. (the "don't delegate" list and the "here are the implementations" list.)

Shouldn't the second impl block be:

impl MagicNumber for B {
   delegate to self.a for *;
}

But there is a question that what is the type of self in delegate statement? B or &B or &mut B?

What about this alternative:

impl MagicNumber for B {
   delegate * to Self::a;
// or
   delegate * to B::a;
}

But when it comes to multiple layers, B::a::c is wrong.

What if the field a is &A type, but there is a method in MaticNumber requires Self type? There should be a clear error message to describe what is wrong.

There is another concern that how delegate interacts with specialization.

There will be an default impl block in the future. Should we allow delegate be used in default impl block? I suppose not, at least for now, to push this RFC as quickly as possible.

When aliasing functions, the declaration of the delegate function is visible; the qualification of self is therefore part of the function arguments list, which will be shared identically between the original function (the one being listed as an alias target) and the alias itself.

For member variables, access will of course depend on context.

Maybe delegate * to self.a can be strict delegation, and default delegate * to self.a can be overridable delegation?

When its a field access its pretty self-explanatory, but its a lot less clear when its an arbitrary expression. Will we really interpret the expression in contexts in which self is a different type depending on the method? The static items issue is also real to me - to literally desugar this, we would need a typeof construct.

I'm not convinced supporting delegating to expressions other than fields is a good idea, which is why I doubt that self is really adding all that much.

One use case I have for this is delegating a trait through an enum, so (grabbing my example from here) I would want to be able to do something like:

struct Foo { ... } // : Read + Write
struct Bar { ... } // : Read + Write

enum Wrapper { Foo(Foo), Bar(Bar) }

impl Wrapper {
    fn read(&mut self) -> &mut Read {
        match *self {
            Wrapper::Foo(ref mut foo) => foo,
            Wrapper::Bar(ref mut bar) => bar,
        }
    }

    fn write(&mut self) -> &mut Write { ... }
}

impl Read for Wrapper { delegate to self.read(); }
impl Write for Wrapper { delegate to self.write(); }

This use case would be subsumed by some sort of anonymous delegating enum support, but there's likely slightly more complicated but similar use-cases where you want to use a method on the delegator to produce the delegatee.

I think there needs to be an extended syntax that allows to provide different delegation targets depending on the form of the self parameter, and with support for mapping returned values.

This might address @withoutboats 's concerns about delegating to non-fields and the meaning of self, which would be explicit in this syntax.

Something like:

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

When its a field access its pretty self-explanatory, but its a lot less clear when its an arbitrary expression. Will we really interpret the expression in contexts in which self is a different type depending on the method?

@withoutboats I never understood what was your issue about this point. There are only three cases to consider self, &self and &mut self and as I stated in the original RFC, delegation would be considered valid if and only if all delegated methods would compile if delegation was replaced by the manually-delegated equivalent code. I don't understand how you could explain it more simply and I really really really doubt it makes the actual implementation of this feature so much complex.

Restricting this to fields only is breaking orthogonality of concepts. This is so sad.

I'm only skimming this thread, but I just wanted to note that Rust does support more flexible "self" expressions, though this support is currently limited to Box<Self> (our intention was to support all manner of "deref-able" things, such as Rc<Self> and so forth, but that probably needs an RFC and definitely needs some impl work to see through to completion).

For example, one can do:

trait Foo {
    fn bar(self: Box<Self>, ...)
}

and that is legal syntax (even works with trait objects).

3 Likes

I’m only skimming this thread, but I just wanted to note that Rust does support more flexible “self” expressions, though this support is currently limited to Box

@nikomatsakis So this is four cases. Now what is the plausibility Rust someday is extended to the other cases? And more generally should a current RFC take into consideration features we might want but for which no design have been proposed so far?

Hi! Sorry for the bump/necro, but I was wondering what happened to delegation.

There was a separate revival thread on it started here a couple of weeks back.