You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is my take in specifying a delegation strategy for rust.
I am unsatisfied with the current proposals and want to share my take on this problem.
Manual delegation requires a lot of code. My example below only implements one of the different implementations of IntoIterator for Vec<T>. It also is not clear by the first glance, that it is just a delegation and does not really introduce new functionality.
structMyVec<T>{inner:Vec<T>}impl<'a,T>IntoIteratorfor&'a MyVec<T>{
delegate IntoIterator::* to &'a self.inner;}
This is a simple example of a delegate implementation. It consists of a delagate keyword followed by it items to delegate (methods and types), a to keyword followed by a "receiver".
In order to have more fine-grained control over what is delegated, the syntax mimics that of a use statement. Therefore you can for example also select specific methods by name instead of everything.
impl<'a,T>IntoIteratorfor&'a MyVec<T>{// if allowed, you can take all types
delegate IntoIterator::{fn::into_iter,type::*} to &'a self.inner;}
impl<'a,T>IntoIteratorfor&'a MyVec<T>{// or you list them by name
delegate IntoIterator::{fn::into_iter,type::{Item,IntoIter}}
to &'a self.inner;// (yes, vertical alignment)}
As far as I know, you can't have a method named type or fn. So this would not overshadow a potential method delegation. Grouping methods and type in their own "name space" allows to only take all methods or types without naming them all (IntoIterator::fn::*).
The receiver currently takes a self parameter. For a classic struct this is not really necessary but for a tuple struct just having a number there would be weird.
impl<T>MyVec<T>{fnget_inner(&self) -> &Vec<T>{self.inner}}impl<'a,T>IntoIteratorfor&'a MyVec<T>{
delegate IntoIterator::* to fn get_inner;}
If the receiver is a method it has to be declared with the fn keyword. The explicit reference is missing since the exact type is declared in the return-type of the function. Also the self parameter must match the type of Self.
A confusion thing with a field-delegation is what the reference on it means. It allows for refinement of the type of the field, to specify exactly to where to delegate. In my example it is &Vec<T> but it could also be a &mut Vec<T> or just a Vec<T>. However it still can delegate to methods which take a different self parameter if possible. For example an owned inner field can be used by value, mutable reference, and immutable reference.
Method-receiver do not make this possible, you would have to have multiple methods for that.
traitExample{fnby_ref(&self);fnby_mut(&mutself);fnby_owner(self);}structInner;implExampleforInner{fnby_ref(&self){todo!()}fnby_mut(&mutself){todo!()}fnby_owner(self){todo!()}}structOuter1(Inner);implExampleforOuter1{
delegate Example::* to self.0;}structOuter2(Inner);implOuter2{fnget_ref(&self) -> &Inner{&self.0}fnget_mut(&mutself) -> &mutInner{&mutself.0}fnget(self) -> Inner{self.0}}implExampleforOuter1{
delegate Example::* to fn get, get_mut, get_mut;}
You can use either one field-receiver or multiple method receiver in on delegation statement. The return type of the first on listed, dictates the delegation target. Also only its type-definition are used and no others. Delegation RFC #2393 used something that looks like a closure, to delegate to something other than a field. But I don't want to introduce a new location where code can be executed. Functions and Closures are enough. I think having something like an auto-referencing of methods-receivers is too confusing. If it turns out useful it can be added later.
Method-receivers need compatible bounds with the current implementation. I believe the compiler can figure that out, but I not 100% about this.
Since it is allowed to only delegate to a specified subset of methods/types a trait implementation could be incomplete. I should be able to still add normal implemented methods/types in an impl with a delegation statement. But delegating to method that is not part of the trait currently implemented should raise a compiler error. In general I think it should be a compiler error if multiple types/methods are defined in an implementation via a delegation. Therefor you are not forced to explicitly list all other methods if you only want to override a small subset. That means manually implemented methods always override delegated ones. But I don't want to allow delegations overriding each another. It would result that the order of definition matters and a reader needs to know what items are in a trait to understand which ends up ending in the implementation.
One thing I want to allow is to delegate an implementation to a separate trait that just happened to have (mostly) the same requirements to be implemented. For example suppose there are two graph libraries that specify a graph trait and operations on them. The operations are different and in my application I want to use both of them, so I implement the trait for on of the libraries and can use that implementation as delegation target for the second one. Or most of it, since I can add the rest manually.
You can also use Self or any other struct instead of a trait to delegate from. Then it should only delegate to accessible methods directly defined in the impl for said struct.
You can also delegate not only in an trait implementation but also in a inherent implementations. Doing so does not include any types if delegated from a trait as the entire type name space is not accessible. This allows pulling some of the methods defined in a trait into the inherent implementation to be used without useing the trait. You wouldn't need the fn name space, but I think for consistency it should still be used. Delegating from a struct in a trait then restricts the usage to only public methods.
Cycles with delegation are possible, but if they occur the manual defined method takes priority.
Associated methods do not take part in the delegation process.
Notes on complexity
There is definite some new complexity involved in writing a delegation. But I think reading it is almost self-explanatory.
Keywords
It requires only two new keywords: delegate and to (could be a weak keyword). The delegate keyword makes sense as it is, but there might be a better alternative to to.
Other syntax
Another advantage is, that this strategy does not modify already existing syntax like RFC #3108 does.
The text was updated successfully, but these errors were encountered:
I think both this and #3108 can be closed. Lang team recently closed #2393 not for the syntax, but for bandwidth concerns, so immediately reopening issues with less informative discussion seems pointless.
A useful step would be a forum or blog post that honestly summarizes the technical discussion in #2393, including that procmacro crates already deliver a substantial portion of this functionality. It's maybe worth noting syntax conflicts like everything use based, but ignore the bikeshed for now.
Another take on delegation
This is my take in specifying a delegation strategy for rust.
I am unsatisfied with the current proposals and want to share my take on this problem.
Manual delegation requires a lot of code. My example below only implements one of the different implementations of
IntoIterator
forVec<T>
. It also is not clear by the first glance, that it is just a delegation and does not really introduce new functionality.My idea
This:
turns into this:
This is a simple example of a delegate implementation. It consists of a
delagate
keyword followed by it items to delegate (methods and types), ato
keyword followed by a "receiver".In order to have more fine-grained control over what is delegated, the syntax mimics that of a
use
statement. Therefore you can for example also select specific methods by name instead of everything.As far as I know, you can't have a method named
type
orfn
. So this would not overshadow a potential method delegation. Grouping methods and type in their own "name space" allows to only take all methods or types without naming them all (IntoIterator::fn::*
).The receiver currently takes a
self
parameter. For a classic struct this is not really necessary but for a tuple struct just having a number there would be weird.If the receiver is a method it has to be declared with the
fn
keyword. The explicit reference is missing since the exact type is declared in the return-type of the function. Also theself
parameter must match the type ofSelf
.A confusion thing with a field-delegation is what the reference on it means. It allows for refinement of the type of the field, to specify exactly to where to delegate. In my example it is
&Vec<T>
but it could also be a&mut Vec<T>
or just aVec<T>
. However it still can delegate to methods which take a differentself
parameter if possible. For example an owned inner field can be used by value, mutable reference, and immutable reference.Method-receiver do not make this possible, you would have to have multiple methods for that.
You can use either one field-receiver or multiple method receiver in on delegation statement. The return type of the first on listed, dictates the delegation target. Also only its type-definition are used and no others. Delegation RFC #2393 used something that looks like a closure, to delegate to something other than a field. But I don't want to introduce a new location where code can be executed. Functions and Closures are enough. I think having something like an auto-referencing of methods-receivers is too confusing. If it turns out useful it can be added later.
Method-receivers need compatible bounds with the current implementation. I believe the compiler can figure that out, but I not 100% about this.
Since it is allowed to only delegate to a specified subset of methods/types a trait implementation could be incomplete. I should be able to still add normal implemented methods/types in an impl with a delegation statement. But delegating to method that is not part of the trait currently implemented should raise a compiler error. In general I think it should be a compiler error if multiple types/methods are defined in an implementation via a delegation. Therefor you are not forced to explicitly list all other methods if you only want to override a small subset. That means manually implemented methods always override delegated ones. But I don't want to allow delegations overriding each another. It would result that the order of definition matters and a reader needs to know what items are in a trait to understand which ends up ending in the implementation.
One thing I want to allow is to delegate an implementation to a separate trait that just happened to have (mostly) the same requirements to be implemented. For example suppose there are two graph libraries that specify a graph trait and operations on them. The operations are different and in my application I want to use both of them, so I implement the trait for on of the libraries and can use that implementation as delegation target for the second one. Or most of it, since I can add the rest manually.
You can also use
Self
or any other struct instead of a trait to delegate from. Then it should only delegate to accessible methods directly defined in the impl for said struct.You can also delegate not only in an trait implementation but also in a inherent implementations. Doing so does not include any types if delegated from a trait as the entire
type
name space is not accessible. This allows pulling some of the methods defined in a trait into the inherent implementation to be used withoutuse
ing the trait. You wouldn't need thefn
name space, but I think for consistency it should still be used. Delegating from a struct in a trait then restricts the usage to only public methods.Cycles with delegation are possible, but if they occur the manual defined method takes priority.
Associated methods do not take part in the delegation process.
Notes on complexity
There is definite some new complexity involved in writing a delegation. But I think reading it is almost self-explanatory.
Keywords
It requires only two new keywords:
delegate
andto
(could be a weak keyword). Thedelegate
keyword makes sense as it is, but there might be a better alternative toto
.Other syntax
Another advantage is, that this strategy does not modify already existing syntax like RFC #3108 does.
The text was updated successfully, but these errors were encountered: