From 80495fb75312e5d517cfad103ff8a91d2f1cea7f Mon Sep 17 00:00:00 2001 From: eddy cizeron Date: Sat, 12 Dec 2015 19:53:20 +0100 Subject: [PATCH 1/7] Rfc: delegation of implementation --- text/0000-delegation-of-implementation.md | 235 ++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 text/0000-delegation-of-implementation.md diff --git a/text/0000-delegation-of-implementation.md b/text/0000-delegation-of-implementation.md new file mode 100644 index 00000000000..695402d7dd0 --- /dev/null +++ b/text/0000-delegation-of-implementation.md @@ -0,0 +1,235 @@ +- Feature Name: delegation_of_implementation +- Start Date: 2015-12-12 +- RFC PR: +- Rust Issue: + +# Summary +[summary]: #summary + +Provide a syntactic sugar to automatically implement a given trait `Tr` using a pre-existing type implementing `Tr`. The purpose is to improve code reuse in rust without damaging the orthogonality of already existing concepts or adding new ones. + +# Motivation +[motivation]: #motivation + +Let's consider some existing pieces of code: +```rust +// from rust/src/test/run-pass/dropck_legal_cycles.rs +impl<'a> Hash for H<'a> { + fn hash(&self, state: &mut H) { + self.name.hash(state) + } +} +``` +```rust +// from servo/components/devtools/actors/timeline.rs +impl Encodable for HighResolutionStamp { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + self.0.encode(s) + } +} +``` +We can see a recurring pattern where the implementation of a method only consists in applying the same method to a subfield or more generally to an expression containing `self`. Those are examples of the well known [composition pattern][object_composition]. It has a lot of advantages but unfortunately it also implies writing explicit boilerplate code again and again. In a classical oop language we could also have opted for inheritance for similar cases. Inheritance comes with its own bunch of problems and limitations but at least it allows a straightforward form of code reuse: any subclass implicitly imports the public methods of its superclass(es). + +Rust has no inheritance (yet) and as a result composition is an even more interesting pattern for factoring code than in other languages. In fact it is already used in many places. Some (approximate) figures: + +Project | Occurences of "delegating methods" | +------------------| ---------------------------------- | +rust-lang/rust | 845 | +rust-lang/cargo | 38 | +servo/servo | 314 | + +It would be an improvement if we alleviate the composition pattern so that it can remain/become a privileged tool for code reuse while being as terse as the inheritance-based equivalent. Related discussions: +* [pre-rfc][pre_rfc] +* [some related reddit thread][comp_over_inh] (I didn't participate in) + +[pre_rfc]: https://internals.rust-lang.org/t/syntactic-sugar-for-delegation-of-implementation/2633 +[comp_over_inh]: https://www.reddit.com/r/rust/comments/372mqw/how_do_i_composition_over_inheritance/ +[object_composition]: https://en.wikipedia.org/wiki/Object_composition + +# Detailed design +[design]: #detailed-design + +## Syntax example + +Let's add a syntactic sugar so that the examples above become: +```rust +impl<'a> Hash for H<'a> use self.name; +``` +and +```rust +impl Encodable for HighResolutionStamp use self.0; +``` + +Again this feature adds no new concept. It just simplify an existing code pattern. However it is interesting to understand the similarities and differences with inheritance. The *delegating type* (`H<'a>` in the first example) implicitely "inherit" methods (`hash`) of the *delegated trait* (`Hash`) from the *surrogate type* (`&'static str` which is the type of the expression `self.name`) like a subclass inherit methods from its superclass(es). A fundamental difference is that the delegating type is not a subtype of the surrogate type in the sense of Liskov. There is no external link between the types. The surrogate may even be less visible than the delegating type. Another difference is that the developer has a total control on which part of the surrogate type to reuse whereas class hierarchy forces him/her to import the entire public interface of the superclass (this is because a superclass plays two roles: the one of the surrogate type and the one of the delegated trait). + +## Partial delegation + +If we consider this code +```rust +// from rust/src/libsyntax/attr.rs +impl AttrMetaMethods for Attribute { + fn check_name(&self, name: &str) -> bool { + let matches = name == &self.name()[..]; + if matches { + mark_used(self); + } + matches + } + fn name(&self) -> InternedString { self.meta().name() } + fn value_str(&self) -> Option { + self.meta().value_str() + } + fn meta_item_list(&self) -> Option<&[P]> { + self.node.value.meta_item_list() + } + fn span(&self) -> Span { self.meta().span } +} +``` +we can identify the recurring expression `self.meta()` but 2 of the 5 methods are more complex. This heterogeneity can be handled simply if we allow partial delegation like in +```rust +impl AttrMetaMethods for Attribute use self.meta() { + fn check_name(&self, name: &str) -> bool { + let matches = name == &self.name()[..]; + if matches { + mark_used(self); + } + matches + } + fn meta_item_list(&self) -> Option<&[P]> { + self.node.value.meta_item_list() + } +} +``` +Only missing methods are automatically implemented. + +In some other cases the compiler just cannot generate the appropriate method. For example when `self` is moved rather than passed by reference, unless the delegating expression produces a result that can itself be moved the borrow checker will complain. In that kind of situations the developer can again provide a custom implementation where necessary and let the compiler handle the rest of the methods. + +## Delegation for other parameters + +If `Self` is used for other parameters, everything works nicely and no specific treatment is required. +```rust +// from rust/src/libcollections/btree/map.rs +impl PartialOrd for BTreeMap { + fn partial_cmp(&self, other: &BTreeMap) -> Option { + self.iter().partial_cmp(other.iter()) + } +} +``` +becomes +```rust +impl PartialOrd for BTreeMap use self.iter(); +``` + +## Associated types/constants + +Unless explicitly set associated types and constants should default to the surrogate implementation value of the corresponding items. + +## Inverse delegating expressions + +Can we handle cases as this one +```rust +// from servo/components/layout/block.rs +impl Clone for BinaryHeap { + fn clone(&self) -> Self { + BinaryHeap { data: self.data.clone() } + } + + fn clone_from(&mut self, source: &Self) { + self.data.clone_from(&source.data); + } +} +``` +where `Self` is used as a return type? Yes but we need a second expression for that. +```rust +impl Clone for BinaryHeap use self.data, BinaryHeap { data: super.clone() }; +``` +Here the `super` keyword corresponds to an instance of the surrogate type. It is the symmetric of `self`. The whole expression must have type `Self`. Both direct and inverse delegating expressions may be given at the same time or possibly just one of them if only one conversion is needed. + +## Combined delegation + +It would be nice if delegation could be combined for multiple traits so that +```rust +// from cargo/src/cargo/core/package_id.rs +impl PartialEq for PackageId { + fn eq(&self, other: &PackageId) -> bool { + (*self.inner).eq(&*other.inner) + } +} +impl PartialOrd for PackageId { + fn partial_cmp(&self, other: &PackageId) -> Option { + (*self.inner).partial_cmp(&*other.inner) + } +} +impl Ord for PackageId { + fn cmp(&self, other: &PackageId) -> Ordering { + (*self.inner).cmp(&*other.inner) + } +} +``` +could be reduced to the single line +```rust +impl PartialEq + PartialOrd + Ord for PackageId use &*self.inner; +``` + +## Function-based delegation + +Sometimes implementations are trait-free but the same pattern is found like in +```rust +// from rust/src/librustc/middle/mem_categorization.rs +impl<'t, 'a,'tcx> MemCategorizationContext<'t, 'a, 'tcx> { + + … + + fn node_ty(&self, id: ast::NodeId) -> McResult> { + self.typer.node_ty(id) + } +} +``` +Here we have no trait to delegate but the same method signatures are reused and semantically the situation is close to a trait-based implementation. A simple possibility could be to introduce a new trait. An alternative is to allow delegation at method level. +```rust +impl<'t, 'a,'tcx> fn node_ty for MemCategorizationContext<'t, 'a, 'tcx> use self.typer; +``` + +## More complex delegation + +`Self` can also appear inside more complex parameter/result types like `Option` or `&[Self]`. If we had HKT in Rust a partial solution based on [functor types][functors] might have been possible. It could still be possible to handle specific cases like precisely options and slices but I have not thought hard about it. The complexity might not be worth it. + +[functors]: https://wiki.haskell.org/Functor + +# Drawbacks +[drawbacks]: #drawbacks + +* It creates some implicit code reuse. This is an intended feature but it could also be considered as dangerous. Modifying traits and surrogate types may automatically import new methods in delegating types with no compiler warning even in cases it is not appropriate (but this issue is the same as modifying a superclass in OOP). +* The benefit may be considered limited for one-method traits. + +# Alternatives +[alternatives]: #alternatives + +## OOP inheritance + +As mentioned before, inheritance can handle similar cases with the advantage its concepts and mechanisms are well known. But with some drawbacks: +* Multiple inheritance is possible but to my knowledge no serious proposition has been made for Rust and I doubt anyone wants to end up with a system as complex and tricky as C++ inheritance (whereas delegation is naturally "multiple delegation") +* As said before inheritance mixes orthogonal concepts (code reuse and subtyping) and does not allow fine grain control over which part of the superclass interface is inherited. + +## Multiple derefs + +Some people noticed a similarity with trait `Deref`. A main limitation is that you can only deref to a single type. However one could imagine implementing multiple derefs by providing the target type as a generic parameter (`Deref`) rather than as an associated type. But again you can find limitations: +* As for inheritance visibility control is impossible: if `B` can be derefed to `A` then the entire public interface of `A` is accessible. +* `Deref` only offers a superficial similarity. If `A` implements trait `Tr`, instances of `B` can sometimes be used where `Tr` is expected but as a counter example a `[B]` array is not assignable to `fn f(t: [T]) where T : Tr`. Derefs do not interact nicely with bounded generic parameters. + +## Compiler plugin + +I was suggested to write a compiler plugin. But I was also told that [type information is not accessible][type_information] (unless you can annotate the surrogate type yourself, which implies you must own it). Moreover I'm not sure a plugin could easily solve the partial delegation cases. + +[type_information]: http://stackoverflow.com/questions/32641466/when-writing-a-syntax-extension-can-i-look-up-information-about-types-other-tha + +## Do nothing + +In the end, it is a syntactic sugar. It just improves the ease of expression, not the capacity to express more concepts. Some simple cases may be handled with deref, others with trait default methods. + +One of my concerns is that the arrival of inheritance in Rust may encourage bad habits. Developers are lazy and DRY principle dissuades them from writing repetitive code. The temptation may be strong to overuse inheritance in situations where only code reuse is required (resulting in unnecessary subtyping hierarchy and uncontrolled interface exposure). + +# Unresolved questions +[unresolved]: #unresolved-questions + +The exact syntax is to be discussed. The proposed one is short but does not name the surrogate type explicitly which may hurt readability. \ No newline at end of file From 99665bbaf9822edfc72087e921775d14ef44512d Mon Sep 17 00:00:00 2001 From: eddy cizeron Date: Sun, 13 Dec 2015 18:34:12 +0100 Subject: [PATCH 2/7] Separate paragraph for possible extensions --- text/0000-delegation-of-implementation.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/text/0000-delegation-of-implementation.md b/text/0000-delegation-of-implementation.md index 695402d7dd0..5ce6286fe7f 100644 --- a/text/0000-delegation-of-implementation.md +++ b/text/0000-delegation-of-implementation.md @@ -124,7 +124,9 @@ impl PartialOrd for BTreeMap use self.iter() Unless explicitly set associated types and constants should default to the surrogate implementation value of the corresponding items. -## Inverse delegating expressions +## Possible extensions + +### Inverse delegating expressions Can we handle cases as this one ```rust @@ -145,7 +147,7 @@ impl Clone for BinaryHeap use self.data, BinaryHeap { data: super.c ``` Here the `super` keyword corresponds to an instance of the surrogate type. It is the symmetric of `self`. The whole expression must have type `Self`. Both direct and inverse delegating expressions may be given at the same time or possibly just one of them if only one conversion is needed. -## Combined delegation +### Combined delegation It would be nice if delegation could be combined for multiple traits so that ```rust @@ -171,7 +173,7 @@ could be reduced to the single line impl PartialEq + PartialOrd + Ord for PackageId use &*self.inner; ``` -## Function-based delegation +### Function-based delegation Sometimes implementations are trait-free but the same pattern is found like in ```rust @@ -190,7 +192,7 @@ Here we have no trait to delegate but the same method signatures are reused and impl<'t, 'a,'tcx> fn node_ty for MemCategorizationContext<'t, 'a, 'tcx> use self.typer; ``` -## More complex delegation +### More complex delegation `Self` can also appear inside more complex parameter/result types like `Option` or `&[Self]`. If we had HKT in Rust a partial solution based on [functor types][functors] might have been possible. It could still be possible to handle specific cases like precisely options and slices but I have not thought hard about it. The complexity might not be worth it. @@ -219,7 +221,7 @@ Some people noticed a similarity with trait `Deref`. A main limitation is that y ## Compiler plugin -I was suggested to write a compiler plugin. But I was also told that [type information is not accessible][type_information] (unless you can annotate the surrogate type yourself, which implies you must own it). Moreover I'm not sure a plugin could easily solve the partial delegation cases. +I was suggested to write a compiler plugin. But I was also told that [type information is not accessible][type_information] (unless you can annotate the delegated trait yourself, which implies you must own it). Moreover I'm not sure a plugin could easily solve the partial delegation cases. [type_information]: http://stackoverflow.com/questions/32641466/when-writing-a-syntax-extension-can-i-look-up-information-about-types-other-tha From f30a62ee9f940e469724c7571b598059a535ef57 Mon Sep 17 00:00:00 2001 From: eddy cizeron Date: Mon, 25 Jan 2016 21:38:45 +0100 Subject: [PATCH 3/7] Typos --- text/0000-delegation-of-implementation.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/text/0000-delegation-of-implementation.md b/text/0000-delegation-of-implementation.md index 5ce6286fe7f..294ff33cca7 100644 --- a/text/0000-delegation-of-implementation.md +++ b/text/0000-delegation-of-implementation.md @@ -38,13 +38,13 @@ rust-lang/rust | 845 | rust-lang/cargo | 38 | servo/servo | 314 | -It would be an improvement if we alleviate the composition pattern so that it can remain/become a privileged tool for code reuse while being as terse as the inheritance-based equivalent. Related discussions: +It would be an improvement if we alleviated the composition pattern so that it can remain/become a privileged tool for code reuse while being as terse as the inheritance-based equivalent. Related discussions: * [pre-rfc][pre_rfc] * [some related reddit thread][comp_over_inh] (I didn't participate in) [pre_rfc]: https://internals.rust-lang.org/t/syntactic-sugar-for-delegation-of-implementation/2633 [comp_over_inh]: https://www.reddit.com/r/rust/comments/372mqw/how_do_i_composition_over_inheritance/ -[object_composition]: https://en.wikipedia.org/wiki/Object_composition +[object_composition]: https://en.wikipedia.org/wiki/Composition_over_inheritance # Detailed design [design]: #detailed-design @@ -60,11 +60,11 @@ and impl Encodable for HighResolutionStamp use self.0; ``` -Again this feature adds no new concept. It just simplify an existing code pattern. However it is interesting to understand the similarities and differences with inheritance. The *delegating type* (`H<'a>` in the first example) implicitely "inherit" methods (`hash`) of the *delegated trait* (`Hash`) from the *surrogate type* (`&'static str` which is the type of the expression `self.name`) like a subclass inherit methods from its superclass(es). A fundamental difference is that the delegating type is not a subtype of the surrogate type in the sense of Liskov. There is no external link between the types. The surrogate may even be less visible than the delegating type. Another difference is that the developer has a total control on which part of the surrogate type to reuse whereas class hierarchy forces him/her to import the entire public interface of the superclass (this is because a superclass plays two roles: the one of the surrogate type and the one of the delegated trait). +Again this feature adds no new concept. It just simplifies an existing code pattern. However it is interesting to understand the similarities and differences with inheritance. The *delegating type* (`H<'a>` in the first example) implicitely "inherits" methods (`hash`) of the *delegated trait* (`Hash`) from the *surrogate type* (`&'static str` which is the type of the *delegating expression* `self.name`) like a subclass inherits methods from its superclass(es). A fundamental difference is that the delegating type is not a subtype of the surrogate type in the sense of Liskov. There is no external link between the types. The surrogate may even be less visible than the delegating type. Another difference is that the developer has a total control on which part of the surrogate type to reuse whereas class hierarchy forces him/her to import the entire public interface of the superclass (this is because a superclass plays two roles: the role of the surrogate type and the role of the delegated trait). ## Partial delegation -If we consider this code +If we consider this piece of code: ```rust // from rust/src/libsyntax/attr.rs impl AttrMetaMethods for Attribute { @@ -102,7 +102,7 @@ impl AttrMetaMethods for Attribute use self.meta() { ``` Only missing methods are automatically implemented. -In some other cases the compiler just cannot generate the appropriate method. For example when `self` is moved rather than passed by reference, unless the delegating expression produces a result that can itself be moved the borrow checker will complain. In that kind of situations the developer can again provide a custom implementation where necessary and let the compiler handle the rest of the methods. +In some other cases the compiler just cannot generate the appropriate method. For example when `self` is moved rather than borrowed, unless the delegating expression produces a result that can itself be moved the borrow checker will complain. In that kind of situations the developer can again provide a custom implementation where necessary and let the compiler handle the rest of the methods. ## Delegation for other parameters @@ -143,7 +143,7 @@ impl Clone for BinaryHeap { ``` where `Self` is used as a return type? Yes but we need a second expression for that. ```rust -impl Clone for BinaryHeap use self.data, BinaryHeap { data: super.clone() }; +impl Clone for BinaryHeap use self.data, BinaryHeap { data: super }; ``` Here the `super` keyword corresponds to an instance of the surrogate type. It is the symmetric of `self`. The whole expression must have type `Self`. Both direct and inverse delegating expressions may be given at the same time or possibly just one of them if only one conversion is needed. @@ -194,7 +194,7 @@ impl<'t, 'a,'tcx> fn node_ty for MemCategorizationContext<'t, 'a, 'tcx> use self ### More complex delegation -`Self` can also appear inside more complex parameter/result types like `Option` or `&[Self]`. If we had HKT in Rust a partial solution based on [functor types][functors] might have been possible. It could still be possible to handle specific cases like precisely options and slices but I have not thought hard about it. The complexity might not be worth it. +`Self` can also appear inside more complex parameter/result types like `Option`, `Box` or `&[Self]`. If we had HKT in Rust a partial solution based on [functor types][functors] might have been possible. It could still be possible to handle specific cases like precisely the ones above but the complexity might not be worth the benefit. [functors]: https://wiki.haskell.org/Functor @@ -210,14 +210,14 @@ impl<'t, 'a,'tcx> fn node_ty for MemCategorizationContext<'t, 'a, 'tcx> use self ## OOP inheritance As mentioned before, inheritance can handle similar cases with the advantage its concepts and mechanisms are well known. But with some drawbacks: -* Multiple inheritance is possible but to my knowledge no serious proposition has been made for Rust and I doubt anyone wants to end up with a system as complex and tricky as C++ inheritance (whereas delegation is naturally "multiple delegation") +* Multiple inheritance is possible but to my knowledge no serious proposition has been made for Rust and I doubt anyone wants to end up with a system as complex and tricky as C++ inheritance (whereas delegation is **naturally multiple delegation**) * As said before inheritance mixes orthogonal concepts (code reuse and subtyping) and does not allow fine grain control over which part of the superclass interface is inherited. ## Multiple derefs Some people noticed a similarity with trait `Deref`. A main limitation is that you can only deref to a single type. However one could imagine implementing multiple derefs by providing the target type as a generic parameter (`Deref`) rather than as an associated type. But again you can find limitations: * As for inheritance visibility control is impossible: if `B` can be derefed to `A` then the entire public interface of `A` is accessible. -* `Deref` only offers a superficial similarity. If `A` implements trait `Tr`, instances of `B` can sometimes be used where `Tr` is expected but as a counter example a `[B]` array is not assignable to `fn f(t: [T]) where T : Tr`. Derefs do not interact nicely with bounded generic parameters. +* `Deref` only offers a superficial similarity. If `A` implements trait `Tr`, instances of `B` can sometimes be used where `Tr` is expected but as a counter example a `&[B]` slice is not assignable to `fn f(t: &[T]) where T : Tr`. Derefs do not interact nicely with bounded generic parameters. ## Compiler plugin From 8dc5b4aff40306f6095b326ad6b6b7521f247363 Mon Sep 17 00:00:00 2001 From: eddy cizeron Date: Mon, 25 Jan 2016 21:24:45 +0100 Subject: [PATCH 4/7] Type dependent surrogate types --- text/0000-delegation-of-implementation.md | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/text/0000-delegation-of-implementation.md b/text/0000-delegation-of-implementation.md index 294ff33cca7..30f6fc31af1 100644 --- a/text/0000-delegation-of-implementation.md +++ b/text/0000-delegation-of-implementation.md @@ -124,6 +124,32 @@ impl PartialOrd for BTreeMap use self.iter() Unless explicitly set associated types and constants should default to the surrogate implementation value of the corresponding items. +## Types and delegation + +All the examples above deal with structs and delegation to subfields. However no restriction is required. Delegating types and surrogates types might be of any kind (structs, tuples, enums, arrays, lambdas, ...) provided it makes sense. An illustrative example with enums: +```rust +enum HTMLColor { White, Silver, Gray, Black, + Red, Maroon, Yellow, Olive, + Lime, Green, Aqua, Teal, + Blue, Navy, Fuchsia, Purple }; + +impl Coordinates for HTMLColor { + fn get_red(&self) -> f32 { ... } + fn get_green(&self) -> f32 { ... } + fn get_blue(&self) -> f32 { ... } + fn get_hue(&self) -> f32 { ... } + fn get_saturation(&self) -> f32 { ... } + fn get_brightness(&self) -> f32 { ... } +} + +enum ThreeBitColor { Black, Blue, Green, Cyan, + Red, Magenta, Yellow, White }; + +fn to_html_color(color: &ThreeBitColor) -> HTMLColor { ... } + +impl Coordinates for ThreeBitColor use to_html_color(&self); +``` + ## Possible extensions ### Inverse delegating expressions @@ -198,6 +224,25 @@ impl<'t, 'a,'tcx> fn node_ty for MemCategorizationContext<'t, 'a, 'tcx> use self [functors]: https://wiki.haskell.org/Functor +### Value-dependent surrogate type + +Let's consider a new example: +```rust +enum TextBoxContent { Number(f64), String(Str) } + +// how to delegate? +impl Hash for TextBoxContent use ??? ; +``` +It seems that in theory we should be able to delegate meaningfully given that for any value of `TextBoxContent` there is an obvious existing implementation for `Hash`. The problem is we cannot select a **single** surrogate type. The actual surrogate type should indeed be chosen based on the runtime value of `Self`. To handle this case I slightly modify the delegation syntax by using a variation of blaenk's proposition: `impl Tr for B use delegatingExpression.impl;`. Now this new syntax could be extended to solve our current issue: +```rust +impl Hash for TextBoxContent use (match self { Number(n) => n.impl, String(s) => s.impl }); +``` +Here the delegating expression can contain several branches that does not need to unify from a type perspective. The `.impl` syntax should be replaced by a call to the actual delegated method. Note that although this pattern may occur naturally with enums it can again apply to any kind of types: +```rust +impl Tr for BStruct use (if self.condition { self.field1.impl } else { self.field2.impl }); +``` +However this kind of delegation for value-dependent surrogate types has a limitation: it does not work for methods with multiple `Self` parameters. Indeed there is no guarantee the runtime values for different parameters will select the same branch and then define a consistent surrogate. + # Drawbacks [drawbacks]: #drawbacks From e486bf1eada28a0c4420dde82097fbc20f2ce9e3 Mon Sep 17 00:00:00 2001 From: contactomorph Date: Sat, 3 Sep 2016 11:49:43 +0200 Subject: [PATCH 5/7] Update 0000-delegation-of-implementation.md --- text/0000-delegation-of-implementation.md | 139 ++++++++++++++++++---- 1 file changed, 114 insertions(+), 25 deletions(-) diff --git a/text/0000-delegation-of-implementation.md b/text/0000-delegation-of-implementation.md index 30f6fc31af1..7f951a5e984 100644 --- a/text/0000-delegation-of-implementation.md +++ b/text/0000-delegation-of-implementation.md @@ -53,11 +53,15 @@ It would be an improvement if we alleviated the composition pattern so that it c Let's add a syntactic sugar so that the examples above become: ```rust -impl<'a> Hash for H<'a> use self.name; +impl<'a> Hash for H<'a> { + use self.name; +} ``` and ```rust -impl Encodable for HighResolutionStamp use self.0; +impl Encodable for HighResolutionStamp { + use self.0; +} ``` Again this feature adds no new concept. It just simplifies an existing code pattern. However it is interesting to understand the similarities and differences with inheritance. The *delegating type* (`H<'a>` in the first example) implicitely "inherits" methods (`hash`) of the *delegated trait* (`Hash`) from the *surrogate type* (`&'static str` which is the type of the *delegating expression* `self.name`) like a subclass inherits methods from its superclass(es). A fundamental difference is that the delegating type is not a subtype of the surrogate type in the sense of Liskov. There is no external link between the types. The surrogate may even be less visible than the delegating type. Another difference is that the developer has a total control on which part of the surrogate type to reuse whereas class hierarchy forces him/her to import the entire public interface of the superclass (this is because a superclass plays two roles: the role of the surrogate type and the role of the delegated trait). @@ -88,6 +92,9 @@ impl AttrMetaMethods for Attribute { we can identify the recurring expression `self.meta()` but 2 of the 5 methods are more complex. This heterogeneity can be handled simply if we allow partial delegation like in ```rust impl AttrMetaMethods for Attribute use self.meta() { + + use self.meta() for name, value_str, span; + fn check_name(&self, name: &str) -> bool { let matches = name == &self.name()[..]; if matches { @@ -100,10 +107,41 @@ impl AttrMetaMethods for Attribute use self.meta() { } } ``` -Only missing methods are automatically implemented. +Only specified methods are automatically implemented with the syntax: +```rust +use delegating_expression for list_of_delegated_methods; +``` In some other cases the compiler just cannot generate the appropriate method. For example when `self` is moved rather than borrowed, unless the delegating expression produces a result that can itself be moved the borrow checker will complain. In that kind of situations the developer can again provide a custom implementation where necessary and let the compiler handle the rest of the methods. +## Mixed partial delegation + +Partial delegation allows an even more powerful pattern: delegating to different surrogate types depending on the method: +```rust +trait Tr { + fn do_something_1( … ); + fn do_something_2( … ); + fn do_something_3( … ); + fn do_something_4( … ); +} + +impl Tr for A { … } +impl Tr for B { … } + +struct C { a: A, b: B, … } + +impl Tr for C { + // do_something_1 is delegated to surrogate type A + use self.a for do_something_1; + + // do_something_1 is delegated to surrogate type B + use self.b for do_something_2, do_something_3; + + // do_something_4 is directly implemented + fn do_something_4( … ) { … } +} +``` + ## Delegation for other parameters If `Self` is used for other parameters, everything works nicely and no specific treatment is required. @@ -117,7 +155,9 @@ impl PartialOrd for BTreeMap { ``` becomes ```rust -impl PartialOrd for BTreeMap use self.iter(); +impl PartialOrd for BTreeMap { + use self.iter(); +} ``` ## Associated types/constants @@ -134,20 +174,22 @@ enum HTMLColor { White, Silver, Gray, Black, Blue, Navy, Fuchsia, Purple }; impl Coordinates for HTMLColor { - fn get_red(&self) -> f32 { ... } - fn get_green(&self) -> f32 { ... } - fn get_blue(&self) -> f32 { ... } - fn get_hue(&self) -> f32 { ... } - fn get_saturation(&self) -> f32 { ... } - fn get_brightness(&self) -> f32 { ... } + fn get_red(&self) -> f32 { … } + fn get_green(&self) -> f32 { … } + fn get_blue(&self) -> f32 { … } + fn get_hue(&self) -> f32 { … } + fn get_saturation(&self) -> f32 { … } + fn get_brightness(&self) -> f32 { … } } enum ThreeBitColor { Black, Blue, Green, Cyan, Red, Magenta, Yellow, White }; -fn to_html_color(color: &ThreeBitColor) -> HTMLColor { ... } +fn to_html_color(color: &ThreeBitColor) -> HTMLColor { … } -impl Coordinates for ThreeBitColor use to_html_color(&self); +impl Coordinates for ThreeBitColor { + use to_html_color(&self); +} ``` ## Possible extensions @@ -169,7 +211,9 @@ impl Clone for BinaryHeap { ``` where `Self` is used as a return type? Yes but we need a second expression for that. ```rust -impl Clone for BinaryHeap use self.data, BinaryHeap { data: super }; +impl Clone for BinaryHeap { + use self.data, BinaryHeap { data: super }; +} ``` Here the `super` keyword corresponds to an instance of the surrogate type. It is the symmetric of `self`. The whole expression must have type `Self`. Both direct and inverse delegating expressions may be given at the same time or possibly just one of them if only one conversion is needed. @@ -194,12 +238,14 @@ impl Ord for PackageId { } } ``` -could be reduced to the single line +could be reduced to: ```rust -impl PartialEq + PartialOrd + Ord for PackageId use &*self.inner; +impl PartialEq + PartialOrd + Ord for PackageId { + use &*self.inner; +} ``` -### Function-based delegation +### Trait-free delegation Sometimes implementations are trait-free but the same pattern is found like in ```rust @@ -213,9 +259,47 @@ impl<'t, 'a,'tcx> MemCategorizationContext<'t, 'a, 'tcx> { } } ``` -Here we have no trait to delegate but the same method signatures are reused and semantically the situation is close to a trait-based implementation. A simple possibility could be to introduce a new trait. An alternative is to allow delegation at method level. +Here we have no trait to delegate but the same method signatures are reused and semantically the situation is close to a trait-based implementation. A simple possibility could be to introduce a new trait. An alternative is to allow delegation even without traits but with the name of the method becoming mandatory: +```rust +impl<'t, 'a,'tcx> MemCategorizationContext<'t, 'a, 'tcx> { + use self.typer for node_ty; +} +``` + +### Renaming delegation + +Maybe the method you want to delegate does not come from the same trait, it also has a distinct original name but it just happens to have the same signature. + +```rust +impl A { + fn do_something(&mut self, predicate: P) -> Option where P: FnMut(&str) -> bool { … } + fn do_something_else(&self) -> i32 { … } + … +} + +trait Tr { + fn select_first(&mut self, predicate: P) -> Option where P: FnMut(&str) -> bool; + fn count(&self) -> i32; + … +} + +fun to_a(b : &B) -> &A { … } + +impl Tr for B { + // here we are mapping distinct methods with same signatures: + // B::select_first <- A::do_something + // B::count <- A::do_something_else + use do_something, do_something_else in to_a(self) for select_first, count; + … +} +``` +The general syntax becomes: +```rust +use list_of_surrogate_methods in delegating_expression for list_of_delegated_methods; +``` +and the previous syntax is now just a shortcut for: ```rust -impl<'t, 'a,'tcx> fn node_ty for MemCategorizationContext<'t, 'a, 'tcx> use self.typer; +use list_of_delegated_methods in delegating_expression for list_of_delegated_methods; ``` ### More complex delegation @@ -228,18 +312,23 @@ impl<'t, 'a,'tcx> fn node_ty for MemCategorizationContext<'t, 'a, 'tcx> use self Let's consider a new example: ```rust -enum TextBoxContent { Number(f64), String(Str) } +enum TextBoxContent { Number(f64), String(&str) } -// how to delegate? -impl Hash for TextBoxContent use ??? ; +impl Hash for TextBoxContent { + use ??? ; // how to delegate? +} ``` -It seems that in theory we should be able to delegate meaningfully given that for any value of `TextBoxContent` there is an obvious existing implementation for `Hash`. The problem is we cannot select a **single** surrogate type. The actual surrogate type should indeed be chosen based on the runtime value of `Self`. To handle this case I slightly modify the delegation syntax by using a variation of blaenk's proposition: `impl Tr for B use delegatingExpression.impl;`. Now this new syntax could be extended to solve our current issue: +It seems that in theory we should be able to delegate meaningfully given that for any value of `TextBoxContent` there is an obvious existing implementation for `Hash`. The problem is we cannot select a **single** surrogate type. The actual surrogate type should indeed be chosen based on the runtime value of `Self`. To handle this case I slightly modify the delegation syntax by using a variation of blaenk's proposition: `impl Tr for B { use delegatingExpression.impl; }`. Now this new syntax could be extended to solve our current issue: ```rust -impl Hash for TextBoxContent use (match self { Number(n) => n.impl, String(s) => s.impl }); +impl Hash for TextBoxContent { + use (match self { Number(n) => n.impl, String(s) => s.impl }); +} ``` Here the delegating expression can contain several branches that does not need to unify from a type perspective. The `.impl` syntax should be replaced by a call to the actual delegated method. Note that although this pattern may occur naturally with enums it can again apply to any kind of types: ```rust -impl Tr for BStruct use (if self.condition { self.field1.impl } else { self.field2.impl }); +impl Tr for BStruct { + use (if self.condition { self.field1.impl } else { self.field2.impl }); +} ``` However this kind of delegation for value-dependent surrogate types has a limitation: it does not work for methods with multiple `Self` parameters. Indeed there is no guarantee the runtime values for different parameters will select the same branch and then define a consistent surrogate. @@ -279,4 +368,4 @@ One of my concerns is that the arrival of inheritance in Rust may encourage bad # Unresolved questions [unresolved]: #unresolved-questions -The exact syntax is to be discussed. The proposed one is short but does not name the surrogate type explicitly which may hurt readability. \ No newline at end of file +The exact syntax is to be discussed. The proposed one is short but does not name the surrogate type explicitly which may hurt readability. From 3658a8284dcec3ed252fced8d49cdb23ed7dd250 Mon Sep 17 00:00:00 2001 From: contactomorph Date: Mon, 5 Sep 2016 09:58:47 +0200 Subject: [PATCH 6/7] Update 0000-delegation-of-implementation.md --- text/0000-delegation-of-implementation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-delegation-of-implementation.md b/text/0000-delegation-of-implementation.md index 7f951a5e984..e37fe2dfbc5 100644 --- a/text/0000-delegation-of-implementation.md +++ b/text/0000-delegation-of-implementation.md @@ -91,7 +91,7 @@ impl AttrMetaMethods for Attribute { ``` we can identify the recurring expression `self.meta()` but 2 of the 5 methods are more complex. This heterogeneity can be handled simply if we allow partial delegation like in ```rust -impl AttrMetaMethods for Attribute use self.meta() { +impl AttrMetaMethods for Attribute { use self.meta() for name, value_str, span; @@ -114,7 +114,7 @@ use delegating_expression for list_of_delegated_methods; In some other cases the compiler just cannot generate the appropriate method. For example when `self` is moved rather than borrowed, unless the delegating expression produces a result that can itself be moved the borrow checker will complain. In that kind of situations the developer can again provide a custom implementation where necessary and let the compiler handle the rest of the methods. -## Mixed partial delegation +### Mixed partial delegation Partial delegation allows an even more powerful pattern: delegating to different surrogate types depending on the method: ```rust From cd5abab449e870261ff51340fadbd1853fdf3173 Mon Sep 17 00:00:00 2001 From: Eddy Cizeron Date: Sun, 2 Apr 2017 17:47:41 +0200 Subject: [PATCH 7/7] Description of type of Self + some reorganization --- text/0000-delegation-of-implementation.md | 96 +++++++++++++++++------ 1 file changed, 70 insertions(+), 26 deletions(-) diff --git a/text/0000-delegation-of-implementation.md b/text/0000-delegation-of-implementation.md index e37fe2dfbc5..3b7130c56c8 100644 --- a/text/0000-delegation-of-implementation.md +++ b/text/0000-delegation-of-implementation.md @@ -66,7 +66,25 @@ impl Encodable for HighResolutionStamp { Again this feature adds no new concept. It just simplifies an existing code pattern. However it is interesting to understand the similarities and differences with inheritance. The *delegating type* (`H<'a>` in the first example) implicitely "inherits" methods (`hash`) of the *delegated trait* (`Hash`) from the *surrogate type* (`&'static str` which is the type of the *delegating expression* `self.name`) like a subclass inherits methods from its superclass(es). A fundamental difference is that the delegating type is not a subtype of the surrogate type in the sense of Liskov. There is no external link between the types. The surrogate may even be less visible than the delegating type. Another difference is that the developer has a total control on which part of the surrogate type to reuse whereas class hierarchy forces him/her to import the entire public interface of the superclass (this is because a superclass plays two roles: the role of the surrogate type and the role of the delegated trait). -## Partial delegation +Other syntaxes have been proposed, including: +```rust +impl<'a> Hash for H<'a> use self.name {} + +#[delegate(self.name)] +impl<'a> Hash for H<'a> {} + +#[delegate(field = name)] +impl<'a> Hash for H<'a> {} + +// on the type itself +struct H<'a> { + #[delegate(Hash)] + name : &'static str, + … +} +``` + +## Partial delegation If we consider this piece of code: ```rust @@ -89,7 +107,7 @@ impl AttrMetaMethods for Attribute { fn span(&self) -> Span { self.meta().span } } ``` -we can identify the recurring expression `self.meta()` but 2 of the 5 methods are more complex. This heterogeneity can be handled simply if we allow partial delegation like in +we can identify the recurring expression `self.meta()` but 2 of the 5 methods are more complex. This heterogeneity can be handled simply if we allow partial delegation like in: ```rust impl AttrMetaMethods for Attribute { @@ -111,12 +129,11 @@ Only specified methods are automatically implemented with the syntax: ```rust use delegating_expression for list_of_delegated_methods; ``` +For other methods the developer can provide a custom implementation. -In some other cases the compiler just cannot generate the appropriate method. For example when `self` is moved rather than borrowed, unless the delegating expression produces a result that can itself be moved the borrow checker will complain. In that kind of situations the developer can again provide a custom implementation where necessary and let the compiler handle the rest of the methods. +### Mixed partial delegation -### Mixed partial delegation - -Partial delegation allows an even more powerful pattern: delegating to different surrogate types depending on the method: +Partial delegation can naturally be extended to an even more powerful pattern: delegating to different surrogate types depending on the method. ```rust trait Tr { fn do_something_1( … ); @@ -134,37 +151,43 @@ impl Tr for C { // do_something_1 is delegated to surrogate type A use self.a for do_something_1; - // do_something_1 is delegated to surrogate type B + // do_something_2 and do_something_3 are delegated to surrogate type B use self.b for do_something_2, do_something_3; // do_something_4 is directly implemented fn do_something_4( … ) { … } } ``` +In this pattern, multiple delegated expressions can be provided with each one declaring a list of methods it is expected to handle. + +## Delegating expressions and the type of `self` -## Delegation for other parameters +In Rust the actual type of the `self` parameter can vary depending on the method in the trait. It is either `Self`, `&mut Self` or `&Self`. This means that: +* each method locally defines the type of `self`; +* for a delegating expression to be valid it must type-check with all delegated methods. -If `Self` is used for other parameters, everything works nicely and no specific treatment is required. +Because inherited mutability apply to expressions like `self.2` and `self.my_field`, they may be used to handle methods where `self` have different type. Furthermore as [auto-referencing and auto-dereferencing][auto] is implicitely applied on method call, the return type of the delegating expression may be transformed if possible to match the expected type of ```self```. However for more complex delegating expression that contains function or method calls with non-convertible return types, such flexibility may not be available. In this case mixed partial delegation can be used to handle differently the groups of methods with respective parameter `self`, `&self` and `&mut self` if needed. + +The proposed rule for validity of a delegating expression `expression` when handling method `my_method` in trait `Trait` is the same as the validity of the plain equivalent code: ```rust -// from rust/src/libcollections/btree/map.rs -impl PartialOrd for BTreeMap { - fn partial_cmp(&self, other: &BTreeMap) -> Option { - self.iter().partial_cmp(other.iter()) +impl Trait for MyType { + fn my_method(self_parameter, other_parameters… ) { + expression.my_method(other_parameters… ) } + … } ``` -becomes -```rust -impl PartialOrd for BTreeMap { - use self.iter(); -} -``` +Note that technically nothing prevents `expression` to contain several occurences of `self` or even none. + +In case the code above contains a type error, the compiler must provide an error message indicating that method `my_method` from trait `Trait` cannot be delegated using `expression` (for example because `my_method` is expecting a `&mut self` but `expression` is returning a result of type `&MySurrogateType`). + +[auto]: https://doc.rust-lang.org/nomicon/dot-operator.html -## Associated types/constants +## Associated items -Unless explicitly set associated types and constants should default to the surrogate implementation value of the corresponding items. +Associated types and constants (or anything that is not a `fn`) are not implicitely delegated. Whenever a trait declares such items, implementations must define their actual values. -## Types and delegation +## About delegating and surrogate types All the examples above deal with structs and delegation to subfields. However no restriction is required. Delegating types and surrogates types might be of any kind (structs, tuples, enums, arrays, lambdas, ...) provided it makes sense. An illustrative example with enums: ```rust @@ -194,6 +217,27 @@ impl Coordinates for ThreeBitColor { ## Possible extensions +Delegation can be extended in a certain number of directions to handle other specific cases. Propositions below are simply invitation for debate and are not part of the main proposition. + +### Delegation for other `Self` parameters + +If method `my_method` contains other parameters with type `Self`, `&Self` or `&mut Self`, delegation may still be possible provided that the delegating expression is simultaneously compatible with all those types. In this case for each such parameter `p` the delegating expression can be transformed by replacing all occurences of `self` by `p`. The resulting expression is then inserted at the position where parameter `p` is normally expected. For example +```rust +// from rust/src/libcollections/btree/map.rs +impl PartialOrd for BTreeMap { + fn partial_cmp(&self, other: &BTreeMap) -> Option { + self.iter().partial_cmp(other.iter()) + } +} +``` +could become +```rust +impl PartialOrd for BTreeMap { + use self.iter(); +} +``` +where the delegating expression `self.iter()` is implicitely transformed into `other.iter()` in order to handle the second parameter of method `partial_cmp`. + ### Inverse delegating expressions Can we handle cases as this one @@ -304,7 +348,7 @@ use list_of_delegated_methods in delegating_expression for list_of_delegated_met ### More complex delegation -`Self` can also appear inside more complex parameter/result types like `Option`, `Box` or `&[Self]`. If we had HKT in Rust a partial solution based on [functor types][functors] might have been possible. It could still be possible to handle specific cases like precisely the ones above but the complexity might not be worth the benefit. +`Self` can also appear inside more complex parameter/result types like `Option`, `Box` or `&[Self]`. When we have higher-kinded types in Rust a partial solution based on [functor types][functors] may been possible. [functors]: https://wiki.haskell.org/Functor @@ -312,7 +356,7 @@ use list_of_delegated_methods in delegating_expression for list_of_delegated_met Let's consider a new example: ```rust -enum TextBoxContent { Number(f64), String(&str) } +enum TextBoxContent { Number(f64), String(&'static str) } impl Hash for TextBoxContent { use ??? ; // how to delegate? @@ -355,7 +399,7 @@ Some people noticed a similarity with trait `Deref`. A main limitation is that y ## Compiler plugin -I was suggested to write a compiler plugin. But I was also told that [type information is not accessible][type_information] (unless you can annotate the delegated trait yourself, which implies you must own it). Moreover I'm not sure a plugin could easily solve the partial delegation cases. +I was suggested to write a compiler plugin. But I was also told that [type information is not accessible][type_information] (unless you can annotate the delegated trait yourself, which implies you must own it). The problem is that delegation requires to analyse the signatures of methods to be delegated and this is as far as I know not currently possible. [type_information]: http://stackoverflow.com/questions/32641466/when-writing-a-syntax-extension-can-i-look-up-information-about-types-other-tha @@ -368,4 +412,4 @@ One of my concerns is that the arrival of inheritance in Rust may encourage bad # Unresolved questions [unresolved]: #unresolved-questions -The exact syntax is to be discussed. The proposed one is short but does not name the surrogate type explicitly which may hurt readability. +The exact syntax is to be discussed. The proposed one is short but does not name the surrogate type explicitly which may hurt readability. As mentioned before plenty of alternate syntaxes have been proposed. I have personally no strong preference. However I would prefer we first agree on the scope and validity rules of delegation before bikeshedding about the exact final syntax. \ No newline at end of file