From 84f78497070a7a27fccdcda0c7fe2e01002fb13a Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Mon, 13 Nov 2023 22:12:50 +0300 Subject: [PATCH 01/16] eRFC: Implement function delegation in rustc --- text/0000-fn-delegation.md | 716 +++++++++++++++++++++++++++++++++++++ 1 file changed, 716 insertions(+) create mode 100644 text/0000-fn-delegation.md diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md new file mode 100644 index 00000000000..437e618d335 --- /dev/null +++ b/text/0000-fn-delegation.md @@ -0,0 +1,716 @@ +- Feature Name: (fill me in with a unique ident, `fn_delegation`) +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Process disclaimer + +In [this comment](https://github.com/rust-lang/rfcs/pull/3513#issuecomment-1771113458) nikomatsakis +says that the eRFC process is dead and the +[experimental feature gates](https://dead-link-to-lang-team-site) process should be preferred for +proposals like this. + +I'll submit the "experimental feature gate" PR to rust-lang/rust as well, but I'm also submitting +a markdown document to this repo to have git history, inline comments, etc. +This document will be linked from that PR. + +# Summary + +This RFC proposes a syntactic sugar for delegating implementations of functions to other already +implemented functions. + +There were two major delegation RFCs in the past, the first RFC in 2015 +() and the second one in 2018 +(). + +The second RFC was postponed by the language team in 2021 +(). +We hope to revive that work again. + +How this proposal is different from the previous ones: +- This proposal follows the "prototype first, finalized design later" approach, so it's oriented +towards compiler team as well, not just language team. +The prototyping is already [in progress](https://github.com/Bryanskiy/rust/tree/delegImpl) and we +are ready to provide resources for getting the feature to production quality if accepted. +- This proposal takes a more data driven approach, and builds the initial design on relatively +detailed statistics about use of delegation-like patterns collected from code in the wild. The +resulting design turns out closer in spirit to the +[original proposal]() by @contactomorph than to later +iterations. + +# Motivation + +This proposal falls under the [Efficient code reuse](https://github.com/rust-lang/rfcs/issues/349) +umbrella. + +Rust doesn't have the sort of data inheritance common for object oriented languages, in which +derived data structure can inherit from some base data structure and automatically use its methods +that way. +In Rust this pattern is typically expressed through composition, in which the "base" data structure +is put into the "derived" data structure as a (possibly nested) field or some similar kind of +sub-object. +Newtypes (`struct Derived(Base);`) are an especially popular example of such pattern. + +With composition methods that in other languages could be potentially inherited automatically need +to be implemented manually (possibly with help of macros). +Such trivial implementations may create a lot of boilerplate, and even prevent people from using +newtypes when it would be appropriate for type safety. + +Some more motivating examples can be found in the two previous RFCs linked above. + +This proposal aims to support a sugar that would allow to avoid such boilerplate in cases +similar to inheritance and newtypes, and in other cases too if they fit into the same basic +mechanism, while staying in limited syntactic budget. + +# Data driven approach + +To drive the design we implemented a compiler pass that collects some metrics about functions +calling other functions. +Based on a combination of such metrics we can detect calls that plausibly look like delegation +from the caller function to the callee function. + +The pass uses linting mechanism in rustc and dumps the data into stderr. +Then a processing script parses those compiler logs and puts the resulting data into +[`pandas.DataFrame`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html) in which +they can then be aggregated and analyzed in different ways. +The data contains `file:line:column` locations, so it's possible to find examples of code with +given properties when necessary. +The code can be found [here](https://github.com/petrochenkov/rust/tree/deleglint2). + +So far the pass was run on crates from rustc and standard library (`x.py build --stage 2`). +That's about 70-80 crates or different kind, both library and application code. +About 267800 calls was collected, and about 5300 of them (2%) looked like delegation. +We can run this on a larger subset of crates.io crates to collect more data, if requested. + +At high level, the "looks like delegation" property is basically a combination of +- whether we can auto-generate it in the compiler, +- whether the inputs and outputs of the caller and callee are compatible enough, and +- the call doesn't already come from some sugar. + +The number of delegation-like calls may be overestimated because we didn't consider compatibility +for things like generic parameters and predicates, or ABI. +Hopefully, this overestimation spreads evenly among different categories of delegation discussed +below. + +The detailed condition can be found in `fn is_delegation` in the rustc branch linked above. +Some collected data will be described in more detail in the design discussion below. + +If we ignore the "source" of the calls then we find that approximately half of the collected +delegation cases come from derives on newtypes (almost all derives was built-in on our sample): + +| source | count | | +|:--------------|---------|----------------------------------:| +| Source.Derive | 5161 | Generated by a `#[derive]` macro | +| Source.User | 4096 | Written directly by user | +| Source.Bang | 1198 | Generated by a `fn_like!()` macro | +| Source.Lang | 1 | Generated by compiler | + +In practice, if you have a trait that provides a corresponding derive macro, and that derive is +sufficient for producing the whole trait impl, then it's preferable to use that derive. +Basically, `#[derive]` is the best delegation sugar, when it's available. +Better delegation support is needed for the remaining cases, not covered by derives like this. +So we remove the derive-generated delegation-like calls from our statistics and base the design on +the remaining cases. + +# Syntactic budget + +We should fit delegation into some syntax that is no more complex than `use` imports. + +```rust +// Import item +#[attrs] +pub(vis) use prefix::{a, b, c as d}; + +// Delegation item +#[attrs] +pub(vis) delegation_keyword prefix::{a, b, c as d} { target_expr_template } +``` + +Our goal is to cover the maximum amount of cases that occur in practice within the given syntax +budget. + +The specific syntactic shape may be rehashed later after getting sufficient user experience, +although the `use`-like shape seems fine as is. +What is important to stay in the budget is to not add additional knobs to it, like pre-processing +or post-processing closures for inputs and outputs, or function signatures, because in that case +delegated functions will get close to full function implementations in wordiness, while staying +worse than them in readability. + +# Design decisions + +## Front matter + +The usual item harness, attributes and visibility, should be supported on delegation items, like on +all other items. +Some attributes may be added (`inline`?) or inherited from the callee, which exactly is an open +question (no statistics were collected about this). + +Unsafety, asyncness, constness and ABI will most likely be inherited from the callee function +(no statistics were collected about these). + +## Renaming + +Renaming `c as d` should be supported, like in imports. + +In the collected data about 40% of delegation instances involve renaming. + +| same name | count | +|:---------------|--------:| +| SameName.TRUE | 3135 | +| SameName.FALSE | 2159 | + +The renaming feature is trivial, so complexity of the support is negligible compared to the gains. + +## Multiple names + +Multiple names in a list `delegation_keyword a, b, c` should be supported, like in imports. + +About 35% delegation items in the collected data are not alone in their impl (or other parent), +there are other delegation items there too. +
+ Details + +| N delegation items | N of imps with this number of delegation items | +|-------------------:|---------------:| +| 1 | 5124 | +| 2 | 279 | +| 3 | 97 | +| 4 | 72 | +| 5 | 49 | +| 10 | 29 | +| 6 | 28 | +| 8 | 17 | +| 7 | 16 | +| 14 | 8 | +| 12 | 4 | +| 9 | 3 | +| 13 | 2 | +| ... | ... | + +
+ +The feature should not be hard to support compared to the gains, unless there are some issues with +cloning `target_expr_template`'s IR or something. In any case, the mechanism will be necessary if +we are going to support higher level sugar like delegation all functions in a trait impl at once +with "glob delegation". + +## Parent context + +Functions delegation should be supported inside both trait impls and inherent impls. +Only about 60% of delegation instances correspond to items in trait impls. + +It should also be supported in other contexts too (free functions, default methods in traits), +because why not - all these cases fit into our code generation scheme (see below) and occur in +practice. +Free function support can be sacrificed if it stops fitting into the scheme, though. +A typical free function delegation case collected from stdarch looks like this. +```rust +#[target_feature(enable = "some_feature")] +pub fn foo(arg: u32) { submodule::foo(arg) } + +// In delegation form +#[target_feature(enable = "some_feature")] +pub delegation_keyword submodule::foo; +``` +Without the attribute difference such "delegation" could just be replaced with a simple reexport +`pub use submodule::foo;`. + +| caller parent | count | +|:--------------------------|--------:| +| CallerParent.TraitImpl | 3124 | +| CallerParent.InherentImpl | 1601 | +| CallerParent.Other | 462 | +| CallerParent.Trait | 107 | + +## First argument transformation aka "target expression" + +First argument (often `self`) should supports arbitrary transformations, rather than just +projection to a field (`self.field`). +In other words, the target expression should be an arbitrary expression. + +| arg0 preproc | count | | +|:-------------------|---------|-------------------:| +| Arg0Preproc.No | 1724 | `arg0` | +| Arg0Preproc.Field | 1580 | `arg0.field` | +| Arg0Preproc.Other | 1407 | anything else | +| Arg0Preproc.Getter | 583 | `arg0.get_field()` | + +`self.field` is not even the most common transformation, usually the first argument is not +transformed at all, although delegation for static methods and free functions skews the statistics +in favor of "no transformation" quite a bit. +Getters (method calls without arguments, `self.get_field()`) are also quite common, and other +target expressions are common too. + +If all static methods and free functions are filtered away, then half of `Arg0Preproc.No`s go away +and the statistics look like this, with `self.field` getting to the top, but still representing +only 36% of cases. + +| arg0 preproc | count | | +|:-------------------|---------|-------------------:| +| Arg0Preproc.Field | 1580 | `self.field` | +| Arg0Preproc.Other | 1360 | anything else | +| Arg0Preproc.No | 830 | `self` | +| Arg0Preproc.Getter | 576 | `self.get_field()` | + +When reading the previous RFCs, I considered support for arbitrary target expressions a no-brainer, +because correctly supporting just fields is likely not any easier, from an implementation point of +view (that's something the implementation experience may clarify). +However, in the feedback to the original RFC it was suggested to limit delegation support to fields, +@contactomorph disagreed with that suggestion and I disagree with it as well so far. + +## Return type compatibility + +Return types should initially be the same in the caller and the callee, then we should try +extending the condition to "same up to `Self` type". + +| ret match | count | | +|:--------------------------|---------|-----------------------------------------------------:| +| RetMatch.Same | 4969 | `fn get_u8() -> u8 { other_get_u8() }` | +| RetMatch.SameUpToSelfType | 317 | `fn foo(&self) -> Newtype { Newtype(self.0.foo()) }` | +| RetMatch.Coerced | 8 | `fn get_ref_u8() -> &u8 { get_ref_mut_u8() }` | + +| ret postproc | count | | +|:------------------|---------|-----------------------------------------------------:| +| RetPostproc.FALSE | 4977 | no result post-processing generally allowed | +| RetPostproc.TRUE | 317 | but `SameUpToSelfType` requires some post-processing | + +### Newtype support + +`RetMatch.SameUpToSelfType` is a case that must be supported in some way, because supporting it +means a proper newtype support. +The number of `RetMatch.SameUpToSelfType` cases found in the collected data is also sizable +(if we didn't filter away delegation cases from `#[derive]`s it would take the first place). + +It usually corresponds to operations like this. +```rust +impl Clone for Newtype { + fn clone(&self) -> Newtype { + Newtype(self.0.clone()) + } +} +``` + +It's very much plausible to consider this case a delegation, but it is a case in which the return +value post-processing is necessary, but we don't want that post-processing to be written by user. +It is likely that it can be done automatically, without growing the syntax budget (like adding +result post-processing closures). +To support this case we need to figure out how to transform `Self` in return types when inheriting +signatures, and how to add the corresponding value post-processing to bodies. + +Perhaps such post-processing can be done through a `From`-like trait that will be implemented +automatically for all newtypes, or at least will be derivable. +If the same trait is implementable manually, then we'll be able to support delegation even for +less matching return types without introducing post-processing closures. + +### Trait impls and coercions + +If the caller is a function in a trait impl, then we can take the function signature from the trait +instead of inheriting it from the callee. +In that case implicit coercion for the return value (`RetMatch.Coerced`) will also make sense, even +if it's not very common - we generate code without any post-processing, and then type checker fills +in the coercion automatically. + +In non-trait-impl cases signatures have to be inherited from the callee (no other place to inherit +them from), so the types will match exactly by construction and no coercions will be supported. + +## Non-first argument transformations + +Non-first arguments should initially have the same type in the caller and the callee, then we +should try extending the condition to "same up to `Self` type". + +| args match | count | | +|:---------------------------|---------|---------------------------------------------------------:| +| ArgsMatch.Same | 4913 | `fn take_u8(&self, a: u8) { self.other_take_u8(a) }` | +| ArgsMatch.SameUpToSelfType | 321 | `fn foo(&self, other: &Self) { self.0.foo(&other.0) }` | +| ArgsMatch.Coerced | 60 | `fn take_vec(&self, a: &Vec) { self.take_slice(a) }` | + +| args preproc | count | | | +|:-------------------|---------|-------------------|----------------------------------------------------:| +| ArgsPreproc.No | 4976 | `arg` | no result post-processing generally allowed | +| ArgsPreproc.Other | 265 | anything else | but `SameUpToSelfType` requires some pre-processing | +| ArgsPreproc.Getter | 34 | `arg.get_field()` | | +| ArgsPreproc.Field | 19 | `arg.field` | | + +### Newtype support + +`ArgsMatch.SameUpToSelfType` is a case that is desirable to support for a proper newtype support. +It usually corresponds to binary operations like this. +```rust +impl Ord for Newtype { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} +``` + +It's quite plausible to consider this case a delegation, but it is a case in which argument +pre-processing is necessary, but we don't want that pre-processing to be written by user. +It is possible that it can be done automatically, without growing the syntax budget (like adding +argument pre-processing closures). +To support this case we need to figure out how to transform `Self` in argument types when inheriting +signatures, and how to add the corresponding argument pre-processing to bodies. + +Perhaps such pre-processing can be done through a `Deref`-like trait that will be implemented +automatically for all newtypes, or at least will be derivable (possibly the same one discussed in +the return type compatibility section above). +If the same trait is implementable manually, then we may be able to support delegation even for +less matching argument types without introducing pre-processing closures. + +### Trait impls and coercions + +If the caller is a function in a trait impl, then we can take the function signature from the trait +instead of inheriting it from the callee. +In that case implicit coercions for argument types (`ArgsMatch.Coerced`) will also make sense, even +if they are not very common - we generate code without any pre-processing, and then type checker +fills in the coercions automatically. + +In non-trait-impl cases signatures have to be inherited from the callee (no other place to inherit +them from), so the types will match exactly by construction and no coercions will be supported. + +## Postponed features + +Extending the delegation feature from functions to associated types/constants to be able to +delegate all items in a trait impl in one go, or the whole trait impl, is not proposed right now. + +Whole trait impl delegation is a second layer of sugar on top of the basic delegation for +individual functions, at least from implementation point of view. +You cannot delegate a whole impl without generating individual delegated functions. + +The second layer can be supported later when the base layer is ready. +It should be useful, delegatable functions in trait impls represent 60% of all delegation cases +after all. +We didn't collect statistics about all functions in a trait impl being delegetable (TODO: collect +them, it's not hard). +We need to make sure that the selected delegation syntax can be extended to this "glob delegation" +case. + +# Rejected features + +Complex post-processing of the returned value (`RetMatch.Different`/`RetPostproc.TRUE`) is not +supported, it needs something like an output post-processing closure, which doesn't fit into the +syntax budget. + +Complex pre-processing of non-first arguments (`ArgsMatch.Different`/`ArgsPreproc.Other`) is not +supported, it needs something like argument pre-processing closures, which doesn't fit into the +syntax budget. + +Delegating to functions with a different number of arguments (`ArgsMatch.DifferentCount`) is not +supported either. + +# Generated body + +So, the generated function body for a delegation item + +```rust +#[attrs] +pub(vis) delegation_keyword name as rename { target_expr_template } +``` + +will look like this (the first argument may be `self`). +```rust +#[attrs] +pub(vis) fn rename(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { + resolved_callee(target_expr_template(arg0), arg1, ..., argN) +} +``` + +How exactly the `resolved_callee` is obtained is a large separate question, it is discussed below. +Substitution of the `target_expr_template` is also discussed below. + +The code generation here is supposed to be "macro-like", that means we generate the code above +(at some high enough IR level), but it is possible that it won't type check and will report errors. +The type checking may also insert implicit code like auto-refs, auto-derefs, or coercions. +The generation cannot happen literally at macro expansion level though, because it needs to inherit +the signature part from the callee (or from some trait), and signatures are not yet ready this +early. + +Support for the `SameUpToSelfType` case (limited automatable pre-processing for non-first arguments +and post-processing for the output) is not yet added to this example. + +# Implementation and design details + +## How the signature is inherited + +### Trait impls, refinement, and coercions + +If our delegation item is in a trait impl `impl Trait for Type { /*delegate foo*/ }` we have two +opportunities: +- Inherit the signature from `Trait::foo`. +- Inherit the signature from the callee, e.g. `SomeFieldType::foo`. + +Right now the signature is always required to match the trait anyway, so there's not much +difference, an error will be reported if the inherited signature doesn't match the trait. + +However, with the ["Refined trait implementations" RFC](https://github.com/rust-lang/rfcs/pull/3245) +accepted the situation becomes more complex. +Signature inherited from the callee may be different from the trait, but that could still be fine +due to refinement. + +- Inheriting signature from the trait means not supporting refinements. +- Inheriting signature from the callee means not supporting coercions +(`RetMatch.Coerced`, `ArgsMatch.Coerced`, see above). + +If our delegation item has any other parent than a trait impl, then we have no other choice than to +inherit the signature from the callee. + +### `Self` mapping + +If the signature is inherited from the callee, then we need to change the callee's `Self` type to +the caller's `Self` type when copying the signature. + +This needs to happen for the `self` parameter at least, but for other parameters and return type +as well if we aim to support the `SameUpToSelfType` cases. + +### `self` parameter + +Whether the first parameter of a function is `self` is a part of signature (or at least part of +public interface), because only functions with `self` can be called using method call syntax. +Multiple cases are possible when inheriting `self`-ness of the first parameter from the callee. + +- If we are in a trait impl and the trait requires `self`, then `self` it is. +- If we are in a trait impl and the trait requires no `self`, then a regular non-self parameter is +used. +- If we are in an inherent impl or a trait, then either `self` or a regular parameter, depending on +whether the callee's `arg0` is `self`. +- If we are in a free function, then a regular parameter is used. + +Alternatively, we just could report `self`-ness mismatches as errors. + +Note, that the `self`'s *type* is always inherited (modulo `Self` mapping), regardless of the +`self`-ness property (except perhaps in cases when we are in a trait impl and the signature is +inherited from the trait). That is required to inherit complex self types like `Pin` correctly. + +### Open questions + +Signature inheritance is a source of a few questions that we cannot answer right now, and that will +need to be answered during implementation. + +- In particular, how are predicates inherited? + - If the callee signature refers to generic parameters defined by the callee's parent item, what do +we do with them exactly? + +- Are signatures to inherit available early enough? + - Ideally we would generate delegation items as HIR, but it is not possible at the moment - all HIR +is generated at once and we cannot add more HIR later. +Generating HIR per-item is probably possible, and it's a goal for people working on incremental +compilation, but it may require some larger infrastructure changes in the compiler. + - Are the signatures available at the astconv time at least? + - If the callee comes from a trait impl `impl Trait for Type { fn foo() }`, then can we take the +`foo`'s signature from that specific impl, rather than from the `Trait`, early enough? +That would mean supporting trait impl refinements on the callee side. + - Passes like variance inference are global and require all signatures to be available at once. +Can delegated items be exempted from this global pass and variance-checked later? +Can it cause infinite cycles with delegation item requiring some other items' signature, and that +other item in its turn requiring the delegation item's signature? + +- Specific mechanism of the `Self` mapping discussed above is not yet clear an needs to also be +figured out during implementation. + +This all will likely need input from people from the types team, and people who implemented +`impl Trait`, which encountered similar issues. + +## How `resolved_callee` is determined + +We should support delegation in two flavors, that differ in the way they determine the callee +function. + +With one flavor the callee path would be specified explicitly, and with another it would be +inferred from `target_expr_template`. +These two flavors can be expressed like this. +```rust +// Explicit paths +delegation_keyword module::name { target_expr_template } +delegation_keyword Type::name { target_expr_template } +delegation_keyword ::name { target_expr_template } + +// Inferred from `target_expr_template` +delegation_keyword name { target_expr_template } +``` + +Doing this makes sense at least from the implementation staging point of view, but it likely makes +sense from the language point of view as well, because in some cases the callee cannot be inferred +from `target_expr_template` (that includes any delegation to free functions in particular, but +that's not the only case). + +### Callee is specified explicitly + +This case is simpler for the compiler. +Path to the callee is known, so it is just used in the generated body. + +```rust +fn name(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { + callee_path(target_expr_template(arg0), arg1, ..., argN) +} +``` + +### Callee is inferred from `target_expr_template` + +#### The proposed algorithm + +- We take the expression `target_expr_template.name()` and turn it into a body for type checking. +- Every `self` in it is assigned the type `Self` and treated as `value_of::()`, i.e. as +"`self` by value". + - That means target expressions involving `*self` are not supported for callee inference +(unless `Self` itself implements `Deref`). Inferring callees having `Pin` in their `self` will +seemingly not work either. Explicit paths will need to be used in these situations. +- Then the body is type checked with the sole purpose of determining the resolution of `name`. + - The resolution doesn't depend on arguments passed to the `.name()` call, or even on their number, +so we can resolve such method call without passing any arguments. + - I think we can resolve static methods this way too, right now `rustc` does it for diagnostics +(if the resolved method doesn't have `self` then an error is reported), but we should be able to +guarantee the precise behavior here too. +This is useful if we want to delegate a list of both static and non-static methods in one go (or to +"glob delegate" the whole trait impl, see postponed features), with inference from +`target_expr_template`. +- Once we have the callee resolution this body is thrown away, type checking results from this +temporary body are not reused in any way when we start type checking the generated code. + +The body is then generated in the same way as with explicit paths, and type checked again. +```rust +fn name(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { + resolved_callee(target_expr_template(arg0), arg1, ..., argN) +} +``` +The only difference is that adjustments on `target_expr_template(arg0)` should be added as if it +was a method receiver, rather than a regular call argument. Otherwise you may have to write +something like `&self.field` in your `target_expr_template` instead of just `self.field`, which is +not good. + +#### The questionable alternative + +Alternatively, we could generate and type check the body like this +```rust +fn name(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { + target_expr_template(arg0).name(arg1, ..., argN) +} +``` +, but then `name` could actually resolve to a different function `resolved_callee2`! +(Given smart enough combination of `target_expr_template` and callee signature). +That doesn't seem good, given that the signature is still inherited from the first +`resolved_callee`. + +#### What if the inferred callee is a static method? + +Another question arises if `resolved_callee` turns out to be a static method. +A simple example: +```rust +// Delegation +impl Newtype { delegation_keyword static_method { self.field } } + +// Generated body +fn static_method(arg: u32) { + FieldType::static_method(arg.field) +} +``` +But that's typically not what we want at all! +`self.field` here is used only for inferring the callee, but in the generated body we want to throw +it away and generate this instead. +```rust +fn from_u32(arg: u32) { + FieldType::static_method(arg) +} +``` + +The solution is to generate a different body if the combination of these two factors happen: +- Delegation flavor with callee inference is chosen +- The inferred callee doesn't actually have a `self` parameter + +Then we generate the alternative body instead +```rust +fn name(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { + resolved_callee(arg0, arg1, ..., argN) +} +``` +, with `target_expr_template` being thrown away. +It's hard to estimate how common this pattern will be by collecting data from existing code, +because `expr.static_method()` doesn't currently compile and produces and error. + +I don't think this case breaks the general rule (`arg0` is transformed using `target_expr_template`), it +rather extends it. +If you need to delegate to a static method, but still transform `arg0`, then the delegation flavor +with explicit path can be used instead. +`arg0` does get transformed occasionally when delegating to functions without `self` +| arg0 preproc | count | +|:-------------------|--------:| +| Arg0Preproc.No | 879 | +| Arg0Preproc.Other | 47 | +| Arg0Preproc.Getter | 7 | + +, so the general scheme may still be useful for this case. + +### Going from `target_expr_template` to generated code + +As mentioned above, when inferring the callee, all `self`s in `target_expr_template` are +effectively replaced with `value_of::()`. + +Similarly, when generating the final body, all `self`s in `target_expr_template` are replaced with +`arg0` of the generated function (which is often also `self`). + +If `arg0` is not transformed, then we could actually skip the target expression part in the +delegation syntax entirely. +```rust +// Explicit path +// For static methods and free functions this looks better than the full form with `{ self }`. +delegation_keyword prefix::name; + +// Inferred callee +// Doesn't make sense semantically because `self.name()` will just infinitely recurse. +delegation_keyword name; +``` + +# Implementation steps + +- 1. Support delegation flavor with explicit paths for the callee, and only for paths that can be +resolved early in `rustc_resolve` (that means `module::name` or `Trait::name`). +This will help us to focus on issues with signature inheritance, and get a working feature while +completely putting aside the questions about callee inference. +The callee signature will also be available very early, before even AST -> HIR lowering. +- 2. Same as above, but with support for type-relative explicit paths `Type::name`. +Now we'll need to be able to generate inherited signatures after some amount of type checking. +- 3. Support delegation flavor with inferred callee. +Need larger amount of type checking before being able to generate inherited signatures. +- 4. Support `RetMatch.SameUpToSelfType` cases. +- 5. Support `ArgsMatch.SameUpToSelfType` cases. +- 6. Consider supporting secondary layers of sugar like "glob delegation". + +Support for a list of multiple delegated functions in a single delegation item can be done in +parallel with any step after `1`. + +# Literature + +- https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming) +- https://kotlinlang.org/docs/delegation.html +- https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/newtype_deriving.html +- https://github.com/rust-lang/rust/issues/7773 (delegation of entire trait implementations) +- https://github.com/rust-lang/rust/issues/8353 (delegation of entire trait implementations, automatically derive traits on newtypes) +- https://github.com/rust-lang/rust/issues/9912 (implementation inheritance via traits) +- https://github.com/rust-lang/rust/issues/19597 (syntax extension to derive traits for a newtype) +- https://github.com/rust-lang/rfcs/pull/186 (newtype keyword) +- https://github.com/rust-lang/rfcs/issues/261 (newtype deriving) +- https://github.com/rust-lang/rfcs/issues/292 (delegation of entire trait implementations) +- https://github.com/rust-lang/rfcs/issues/299 (implementation inheritance via traits) +- https://github.com/rust-lang/rfcs/issues/349 (efficient code reuse) +- https://github.com/rust-lang/rfcs/issues/479 (delegation of entire trait implementations, automatically derive traits on newtypes) +- https://github.com/rust-lang/rfcs/pull/508 (newtype deriving) +- https://github.com/rust-lang/rfcs/pull/949 (castable newtypes) +- https://github.com/rust-lang/rfcs/pull/1406 (delegation of implementation) - main RFC +- https://github.com/rust-lang/rfcs/pull/1546 (fields in traits) +- https://github.com/rust-lang/rfcs/pull/2242 (semantic newtypes) +- https://github.com/rust-lang/rfcs/pull/2375 (inherent trait implementation) +- https://github.com/rust-lang/rfcs/pull/2393 (delegation) - rework of main RFC +- https://github.com/rust-lang/rfcs/pull/2429 (reserve delegate keyword) +- https://github.com/rust-lang/rfcs/pull/2874 (trait enums) +- https://github.com/rust-lang/rfcs/issues/3108 (delegation dup) +- https://github.com/rust-lang/rfcs/issues/3133 (delegation another dup) +- https://internals.rust-lang.org/t/syntactic-sugar-for-delegation-of-implementation/2633 (preface to main rfc) +- https://internals.rust-lang.org/t/3-weeks-to-delegation-please-help/5742 (preface to main rfc rework) +- https://internals.rust-lang.org/t/new-rfc-for-delegation-anyone-interested-in-contributing/6644 (preface to main rfc rework 2) +- https://internals.rust-lang.org/t/potential-rfc-delegation/13831 (dup) +- https://internals.rust-lang.org/t/pre-rfc-forwarding/13836 (dup) +- https://internals.rust-lang.org/t/adding-true-oo-capabilities-to-rust/16691 (tangentially related) +- https://internals.rust-lang.org/t/is-it-still-possible-to-make-progress-on-postponed-features-the-case-of-delegation/16868 (status update on 2 main RFCs N years later) +- https://internals.rust-lang.org/t/strong-type-aliases-type-copy/17754 (dup) +- https://crates.io/crates/delegate (fn delegation, lots of features and syntax, post- and pre-processing, alive) +- https://crates.io/crates/delegate-attr (fn delegation, a bit simpler than delegate, dead) +- https://crates.io/crates/ambassador (traits only, trait prototype registering, not very alive) +- https://crates.io/crates/enum_delegate (trait <-> enum polymorphism specifically, trait prototype registering, alive) From c7a328d10bfa6eb3946d2244d532119e4bd70752 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 16 Nov 2023 18:29:11 +0300 Subject: [PATCH 02/16] Add some missing statistics Add a possible use of `#[refine]` Fix a dead link --- text/0000-fn-delegation.md | 51 ++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 437e618d335..666cf9a5a0d 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -7,8 +7,8 @@ In [this comment](https://github.com/rust-lang/rfcs/pull/3513#issuecomment-1771113458) nikomatsakis says that the eRFC process is dead and the -[experimental feature gates](https://dead-link-to-lang-team-site) process should be preferred for -proposals like this. +[experimental feature gates](https://github.com/rust-lang/lang-team/blob/master/src/how_to/experiment.md) +process should be preferred for proposals like this. I'll submit the "experimental feature gate" PR to rust-lang/rust as well, but I'm also submitting a markdown document to this repo to have git history, inline comments, etc. @@ -228,12 +228,13 @@ First argument (often `self`) should supports arbitrary transformations, rather projection to a field (`self.field`). In other words, the target expression should be an arbitrary expression. -| arg0 preproc | count | | -|:-------------------|---------|-------------------:| -| Arg0Preproc.No | 1724 | `arg0` | -| Arg0Preproc.Field | 1580 | `arg0.field` | -| Arg0Preproc.Other | 1407 | anything else | -| Arg0Preproc.Getter | 583 | `arg0.get_field()` | +| arg0 preproc | count | | +|:---------------------|---------|--------------------:| +| Arg0Preproc.No | 1734 | `arg0` | +| Arg0Preproc.Field | 1582 | `arg0.field` | +| Arg0Preproc.Other | 1270 | anything else | +| Arg0Preproc.Getter | 583 | `arg0.get_field()` | +| Arg0Preproc.RefField | 136 | `&(mut) arg0.field` | `self.field` is not even the most common transformation, usually the first argument is not transformed at all, although delegation for static methods and free functions skews the statistics @@ -245,12 +246,13 @@ If all static methods and free functions are filtered away, then half of `Arg0Pr and the statistics look like this, with `self.field` getting to the top, but still representing only 36% of cases. -| arg0 preproc | count | | -|:-------------------|---------|-------------------:| -| Arg0Preproc.Field | 1580 | `self.field` | -| Arg0Preproc.Other | 1360 | anything else | -| Arg0Preproc.No | 830 | `self` | -| Arg0Preproc.Getter | 576 | `self.get_field()` | +| arg0 preproc | count | | +|:---------------------|---------|--------------------:| +| Arg0Preproc.Field | 1582 | `self.field` | +| Arg0Preproc.Other | 1223 | anything else | +| Arg0Preproc.No | 833 | `self` | +| Arg0Preproc.Getter | 576 | `self.get_field()` | +| Arg0Preproc.RefField | 136 | `&(mut) arg0.field` | When reading the previous RFCs, I considered support for arbitrary target expressions a no-brainer, because correctly supporting just fields is likely not any easier, from an implementation point of @@ -324,12 +326,13 @@ should try extending the condition to "same up to `Self` type". | ArgsMatch.SameUpToSelfType | 321 | `fn foo(&self, other: &Self) { self.0.foo(&other.0) }` | | ArgsMatch.Coerced | 60 | `fn take_vec(&self, a: &Vec) { self.take_slice(a) }` | -| args preproc | count | | | -|:-------------------|---------|-------------------|----------------------------------------------------:| -| ArgsPreproc.No | 4976 | `arg` | no result post-processing generally allowed | -| ArgsPreproc.Other | 265 | anything else | but `SameUpToSelfType` requires some pre-processing | -| ArgsPreproc.Getter | 34 | `arg.get_field()` | | -| ArgsPreproc.Field | 19 | `arg.field` | | +| args preproc | count | | | +|:---------------------|---------|---------------------|----------------------------------------------------:| +| ArgsPreproc.No | 4987 | `arg` | no result pre-processing generally allowed | +| ArgsPreproc.Other | 226 | anything else | but `SameUpToSelfType` requires some pre-processing | +| ArgsPreproc.RefField | 39 | `&(mut) arg0.field` | | +| ArgsPreproc.Getter | 34 | `arg.get_field()` | | +| ArgsPreproc.Field | 19 | `arg.field` | | ### Newtype support @@ -379,8 +382,9 @@ You cannot delegate a whole impl without generating individual delegated functio The second layer can be supported later when the base layer is ready. It should be useful, delegatable functions in trait impls represent 60% of all delegation cases after all. -We didn't collect statistics about all functions in a trait impl being delegetable (TODO: collect -them, it's not hard). +In our collected data about 48% of trait impls in which at least one function was delegatable +could be delegated entirely, 38% contained associated types or constants but all functions in them +could be delegated, and 14% could only be delegated partially. We need to make sure that the selected delegation syntax can be extended to this "glob delegation" case. @@ -450,6 +454,9 @@ due to refinement. - Inheriting signature from the callee means not supporting coercions (`RetMatch.Coerced`, `ArgsMatch.Coerced`, see above). +The `#[refine]` attribute proposed by RFC 3245 could potentially be used for changing the behavior +from one to another. We suggest inheriting signatures from the trait by default. + If our delegation item has any other parent than a trait impl, then we have no other choice than to inherit the signature from the callee. From 084cad4f4ddcd3c213294c4c78309ca20ed18bb8 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 23 Nov 2023 18:36:42 +0300 Subject: [PATCH 03/16] Remove the process disclaimer --- text/0000-fn-delegation.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 666cf9a5a0d..1615a9d6ead 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -3,17 +3,6 @@ - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) -# Process disclaimer - -In [this comment](https://github.com/rust-lang/rfcs/pull/3513#issuecomment-1771113458) nikomatsakis -says that the eRFC process is dead and the -[experimental feature gates](https://github.com/rust-lang/lang-team/blob/master/src/how_to/experiment.md) -process should be preferred for proposals like this. - -I'll submit the "experimental feature gate" PR to rust-lang/rust as well, but I'm also submitting -a markdown document to this repo to have git history, inline comments, etc. -This document will be linked from that PR. - # Summary This RFC proposes a syntactic sugar for delegating implementations of functions to other already From e549707d579fc79db38ef17eb1e92883980926fc Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sat, 10 Oct 2026 16:02:07 +0300 Subject: [PATCH 04/16] Replace the old RFC with the new RFC --- text/0000-fn-delegation.md | 2479 +++++++++++++++++++++++++++--------- 1 file changed, 1899 insertions(+), 580 deletions(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 1615a9d6ead..942dcf568fe 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -1,712 +1,2031 @@ + - Feature Name: (fill me in with a unique ident, `fn_delegation`) - Start Date: (fill me in with today's date, YYYY-MM-DD) - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) -# Summary - -This RFC proposes a syntactic sugar for delegating implementations of functions to other already -implemented functions. - -There were two major delegation RFCs in the past, the first RFC in 2015 -() and the second one in 2018 -(). - -The second RFC was postponed by the language team in 2021 -(). -We hope to revive that work again. - -How this proposal is different from the previous ones: -- This proposal follows the "prototype first, finalized design later" approach, so it's oriented -towards compiler team as well, not just language team. -The prototyping is already [in progress](https://github.com/Bryanskiy/rust/tree/delegImpl) and we -are ready to provide resources for getting the feature to production quality if accepted. -- This proposal takes a more data driven approach, and builds the initial design on relatively -detailed statistics about use of delegation-like patterns collected from code in the wild. The -resulting design turns out closer in spirit to the -[original proposal]() by @contactomorph than to later -iterations. - -# Motivation - -This proposal falls under the [Efficient code reuse](https://github.com/rust-lang/rfcs/issues/349) -umbrella. - -Rust doesn't have the sort of data inheritance common for object oriented languages, in which -derived data structure can inherit from some base data structure and automatically use its methods -that way. -In Rust this pattern is typically expressed through composition, in which the "base" data structure -is put into the "derived" data structure as a (possibly nested) field or some similar kind of -sub-object. -Newtypes (`struct Derived(Base);`) are an especially popular example of such pattern. - -With composition methods that in other languages could be potentially inherited automatically need -to be implemented manually (possibly with help of macros). -Such trivial implementations may create a lot of boilerplate, and even prevent people from using -newtypes when it would be appropriate for type safety. - -Some more motivating examples can be found in the two previous RFCs linked above. - -This proposal aims to support a sugar that would allow to avoid such boilerplate in cases -similar to inheritance and newtypes, and in other cases too if they fit into the same basic -mechanism, while staying in limited syntactic budget. - -# Data driven approach - -To drive the design we implemented a compiler pass that collects some metrics about functions -calling other functions. -Based on a combination of such metrics we can detect calls that plausibly look like delegation -from the caller function to the callee function. - -The pass uses linting mechanism in rustc and dumps the data into stderr. -Then a processing script parses those compiler logs and puts the resulting data into -[`pandas.DataFrame`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html) in which -they can then be aggregated and analyzed in different ways. -The data contains `file:line:column` locations, so it's possible to find examples of code with -given properties when necessary. -The code can be found [here](https://github.com/petrochenkov/rust/tree/deleglint2). - -So far the pass was run on crates from rustc and standard library (`x.py build --stage 2`). -That's about 70-80 crates or different kind, both library and application code. -About 267800 calls was collected, and about 5300 of them (2%) looked like delegation. -We can run this on a larger subset of crates.io crates to collect more data, if requested. - -At high level, the "looks like delegation" property is basically a combination of -- whether we can auto-generate it in the compiler, -- whether the inputs and outputs of the caller and callee are compatible enough, and -- the call doesn't already come from some sugar. - -The number of delegation-like calls may be overestimated because we didn't consider compatibility -for things like generic parameters and predicates, or ABI. -Hopefully, this overestimation spreads evenly among different categories of delegation discussed -below. - -The detailed condition can be found in `fn is_delegation` in the rustc branch linked above. -Some collected data will be described in more detail in the design discussion below. - -If we ignore the "source" of the calls then we find that approximately half of the collected -delegation cases come from derives on newtypes (almost all derives was built-in on our sample): - -| source | count | | -|:--------------|---------|----------------------------------:| -| Source.Derive | 5161 | Generated by a `#[derive]` macro | -| Source.User | 4096 | Written directly by user | -| Source.Bang | 1198 | Generated by a `fn_like!()` macro | -| Source.Lang | 1 | Generated by compiler | - -In practice, if you have a trait that provides a corresponding derive macro, and that derive is -sufficient for producing the whole trait impl, then it's preferable to use that derive. -Basically, `#[derive]` is the best delegation sugar, when it's available. -Better delegation support is needed for the remaining cases, not covered by derives like this. -So we remove the derive-generated delegation-like calls from our statistics and base the design on -the remaining cases. - -# Syntactic budget - -We should fit delegation into some syntax that is no more complex than `use` imports. +## Summary +[summary]: #summary + +This RFC proposes a design for _delegation_: syntactic sugar for ergonomically forwarding function calls. + +## Motivation + +### Forwarding to a subobject +Rust [deliberately](https://doc.rust-lang.org/book/ch18-01-what-is-oo.html#inheritance-as-a-type-system-and-as-code-sharing) does not provide the kind of data inheritance common in object-oriented languages where a derived type automatically inherits methods from a base type. Instead, Rust typically expresses this pattern through composition: the "base" type is embedded inside the "derived" type as a field (possibly nested) or another form of subobject. With composition, methods that would be inherited automatically in other languages must instead be implemented manually, often with the help of macros. Consider a common pattern [found](https://github.com/rust-lang/rust/blob/ad2e756c7093149e25f67a747e579a49b7e6976e/library/core/src/iter/adapters/flatten.rs#L55-L104) throughout real Rust codebases: + + ```rust -// Import item -#[attrs] -pub(vis) use prefix::{a, b, c as d}; +impl Iterator for FlatMap +where + F: FnMut(I::Item) -> U, +{ + fn next(&mut self) -> Option { + self.inner.next() + } -// Delegation item -#[attrs] -pub(vis) delegation_keyword prefix::{a, b, c as d} { target_expr_template } + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn advance_by(&mut self, n: usize) -> Result<(), NonZero> { + self.inner.advance_by(n) + } + + fn count(self) -> usize { + self.inner.count() + } + + fn last(self) -> Option { + self.inner.last() + } + ... +} ``` -Our goal is to cover the maximum amount of cases that occur in practice within the given syntax -budget. +The `Iterator` implementation simply forwards multiple method calls to a field that already implements that trait. The pattern is particularly common with newtypes, which often need to reintroduce many of the inner type's inherent methods or trait implementations. -The specific syntactic shape may be rehashed later after getting sufficient user experience, -although the `use`-like shape seems fine as is. -What is important to stay in the budget is to not add additional knobs to it, like pre-processing -or post-processing closures for inputs and outputs, or function signatures, because in that case -delegated functions will get close to full function implementations in wordiness, while staying -worse than them in readability. +This situation highlights a gap in Rust’s ergonomics: while Rust provides powerful mechanisms for defining abstractions through traits and generics, it offers comparatively little support for reusing existing behavior. -# Design decisions +This RFC aims to address this limitation by introducing a delegation feature. With delegation, the forwarding implementation above could be rewritten as follows: -## Front matter + +```rust +impl Iterator for FlatMap +where + F: FnMut(I::Item) -> U, +{ + type Item = I::Item; + reuse Iterator::* { self.inner } +} +``` -The usual item harness, attributes and visibility, should be supported on delegation items, like on -all other items. -Some attributes may be added (`inline`?) or inherited from the callee, which exactly is an open -question (no statistics were collected about this). +Delegation has long been discussed by the Rust community: it has motivated two prior RFCs ([rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406), [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393)), multiple conversations, and several macro crates. See [_Prior art_](#prior-art) for an overview of these efforts. This proposal seeks to revive that work. -Unsafety, asyncness, constness and ABI will most likely be inherited from the callee function -(no statistics were collected about these). +### Generalization -## Renaming +While forwarding to subobject methods remains the main motivating scenario, a sufficiently general mechanism for forwarding function calls can support other scenarios as well. +- An inherent method on a type forwarding to a method from a trait implementation on the same type. +- A "reexport on steroids" that adds attributes to an existing function definition. + - For example, target feature attributes, like it often happens in [stdarch](https://github.com/rust-lang/stdarch). +- Any other scenario that takes the general form of a function calling another function with limited argument transformation. -Renaming `c as d` should be supported, like in imports. +This part of the motivation is a lesson drawn directly from the two prior attempts at delegation. Both [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406) and [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393) restricted delegation in some form, leaving multiple possible delegation patterns as future extensions. In both cases, the forward-compatibility concerns were never addressed. Therefore, in this proposal we want to explore the design space more thoroughly. -In the collected data about 40% of delegation instances involve renaming. +## How to read this RFC -| same name | count | -|:---------------|--------:| -| SameName.TRUE | 3135 | -| SameName.FALSE | 2159 | +This RFC is quite long, and a few kinds of cross-references recur throughout it, so it's worth spelling out the convention up front: -The renaming feature is trivial, so complexity of the support is negligible compared to the gains. +- A ([?](#anchor)) link points to a rationale subsection under “Rationale and alternatives” explaining why a design decision was made the way it was. These are asides: skipping them does not affect your understanding of the feature itself, only of the reasoning behind a specific choice. +- A [_text in italics_](#anchor) link points to another section of the RFC: material the current paragraph depends on. +- A plain [text](url) link points to a resource outside this RFC, such as a pull request, issue, comment, crate, or page in the Rust Reference. -## Multiple names +### Terminology -Multiple names in a list `delegation_keyword a, b, c` should be supported, like in imports. +The following terminology is frequently used in this proposal: -About 35% delegation items in the collected data are not alone in their impl (or other parent), -there are other delegation items there too. -
- Details - -| N delegation items | N of imps with this number of delegation items | -|-------------------:|---------------:| -| 1 | 5124 | -| 2 | 279 | -| 3 | 97 | -| 4 | 72 | -| 5 | 49 | -| 10 | 29 | -| 6 | 28 | -| 8 | 17 | -| 7 | 16 | -| 14 | 8 | -| 12 | 4 | -| 9 | 3 | -| 13 | 2 | -| ... | ... | +- _delegation item_ - a new item kind introduced by this proposal, declared with the `reuse` keyword, that generates a function or method that forwards its arguments to the specified callee. +- _target block_ - an optional block expression whose trailing expression transforms some of the generated function's arguments before those arguments are forwarded to the callee; usually, the transformed argument is the method receiver. +- _target expression_ - the trailing expression of the target block, if it exists. +- _parent context_ - the parent item in which the delegation item appears. This can be a module or block (for free functions), a trait implementation, an inherent implementation, or a trait definition (for associated functions). +- _desugaring_ - transformation of a delegation item into a regular function definition with a signature and a body. +- _renaming_ - the ability to give the generated function a name that differs from the callee's name. +- _delegation pattern_ - a piece of code that can potentially be rewritten using a delegation item. +- _delegation resolution_ - a function definition from which the signature is copied during desugaring. -
+## Implementation experience + +This RFC draws on the experimental implementation tracked in [rust-lang/rust#118212](https://github.com/rust-lang/rust/issues/118212). + +Many of the examples in this proposal can be tried on nightly Rust. + +The nightly implementation is [feature-complete](https://en.wikipedia.org/wiki/Software_release_life_cycle#Feature-complete) and may even accept more code than this RFC describes, since its primary purpose was experimentation. +Different parts of the implementation may have different levels of design maturity and polish. If the feature is stabilized, stabilization will definitely take place in multiple stages. + +Some delegation subfeatures, such as delegation to inherent methods, may work in a limited way, since supporting them properly would require compiler reengineering to avoid query cycles. Some of these limitations are discussed throughout the proposal. + +## Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Suppose you're writing a `BTreeSet` type as a wrapper around `BTreeMap`, which is, incidentally, close to how the standard library's own `BTreeSet` is built (the real `BTreeSet` also carries an allocator parameter, elided here for simplicity). + +```rust +pub struct BTreeSet { + map: BTreeMap, +} +``` + +Here is the forwarding implementation of the `Hash` trait for `BTreeSet`: + +```rust +impl Hash for BTreeSet { + fn hash(&self, state: &mut H) { + self.map.hash(state) + } +} +``` + +With delegation, the same implementation could look like this: + +```rust +impl Hash for BTreeSet { + reuse Hash::hash { self.map } +} +``` + +The `reuse` item is a delegation item that desugars into a function definition. `Hash::hash` is the callee to which the delegation item forwards, and `{ self.map }` is the target block: a small block whose trailing expression is applied to some of the callee’s arguments, usually the receiver. + +> [!NOTE] +> +> This example and some examples below can be expressed more efficiently using `derive` because they use traits from the standard library, but here we are using delegation for them to demonstrate what is possible. + +### Paths and callee disambiguation + +One might ask why we need to specify the path `Hash::hash` instead of simply writing `hash` in the previous example. The reason is that the callee does not have to be a method of the wrapped type, so the name alone would not tell which function is meant. For example, the `IntoIterator` implementation of `BTreeSet` simply calls the `iter` inherent method of `BTreeSet` itself: + +```rust +impl<'a, T> IntoIterator for &'a BTreeSet { + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + fn into_iter(self) -> Iter<'a, T> { + self.iter() + } +} +``` + +With delegation, the `into_iter` implementation may be replaced as follows: + +```rust +impl<'a, T> IntoIterator for &'a BTreeSet { + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + reuse BTreeSet::::iter as into_iter { self } +} +``` + +Here, the `as into_iter` part gives the generated function the name the trait requires (see [_Renaming a delegated method_](#renaming-a-delegated-method) below). The target block `{ self }` just passes the receiver through unchanged. + +Paths unambiguously identify the function to which we are forwarding. When delegating to type-relative paths, as with `BTreeSet::::iter` above, you currently need to specify the type's generic arguments. This limitation could be removed in the future. + +### Other parent contexts -The feature should not be hard to support compared to the gains, unless there are some issues with -cloning `target_expr_template`'s IR or something. In any case, the mechanism will be necessary if -we are going to support higher level sugar like delegation all functions in a trait impl at once -with "glob delegation". - -## Parent context - -Functions delegation should be supported inside both trait impls and inherent impls. -Only about 60% of delegation instances correspond to items in trait impls. - -It should also be supported in other contexts too (free functions, default methods in traits), -because why not - all these cases fit into our code generation scheme (see below) and occur in -practice. -Free function support can be sacrificed if it stops fitting into the scheme, though. -A typical free function delegation case collected from stdarch looks like this. -```rust -#[target_feature(enable = "some_feature")] -pub fn foo(arg: u32) { submodule::foo(arg) } - -// In delegation form -#[target_feature(enable = "some_feature")] -pub delegation_keyword submodule::foo; -``` -Without the attribute difference such "delegation" could just be replaced with a simple reexport -`pub use submodule::foo;`. - -| caller parent | count | -|:--------------------------|--------:| -| CallerParent.TraitImpl | 3124 | -| CallerParent.InherentImpl | 1601 | -| CallerParent.Other | 462 | -| CallerParent.Trait | 107 | - -## First argument transformation aka "target expression" - -First argument (often `self`) should supports arbitrary transformations, rather than just -projection to a field (`self.field`). -In other words, the target expression should be an arbitrary expression. - -| arg0 preproc | count | | -|:---------------------|---------|--------------------:| -| Arg0Preproc.No | 1734 | `arg0` | -| Arg0Preproc.Field | 1582 | `arg0.field` | -| Arg0Preproc.Other | 1270 | anything else | -| Arg0Preproc.Getter | 583 | `arg0.get_field()` | -| Arg0Preproc.RefField | 136 | `&(mut) arg0.field` | - -`self.field` is not even the most common transformation, usually the first argument is not -transformed at all, although delegation for static methods and free functions skews the statistics -in favor of "no transformation" quite a bit. -Getters (method calls without arguments, `self.get_field()`) are also quite common, and other -target expressions are common too. - -If all static methods and free functions are filtered away, then half of `Arg0Preproc.No`s go away -and the statistics look like this, with `self.field` getting to the top, but still representing -only 36% of cases. - -| arg0 preproc | count | | -|:---------------------|---------|--------------------:| -| Arg0Preproc.Field | 1582 | `self.field` | -| Arg0Preproc.Other | 1223 | anything else | -| Arg0Preproc.No | 833 | `self` | -| Arg0Preproc.Getter | 576 | `self.get_field()` | -| Arg0Preproc.RefField | 136 | `&(mut) arg0.field` | - -When reading the previous RFCs, I considered support for arbitrary target expressions a no-brainer, -because correctly supporting just fields is likely not any easier, from an implementation point of -view (that's something the implementation experience may clarify). -However, in the feedback to the original RFC it was suggested to limit delegation support to fields, -@contactomorph disagreed with that suggestion and I disagree with it as well so far. - -## Return type compatibility - -Return types should initially be the same in the caller and the callee, then we should try -extending the condition to "same up to `Self` type". - -| ret match | count | | -|:--------------------------|---------|-----------------------------------------------------:| -| RetMatch.Same | 4969 | `fn get_u8() -> u8 { other_get_u8() }` | -| RetMatch.SameUpToSelfType | 317 | `fn foo(&self) -> Newtype { Newtype(self.0.foo()) }` | -| RetMatch.Coerced | 8 | `fn get_ref_u8() -> &u8 { get_ref_mut_u8() }` | - -| ret postproc | count | | -|:------------------|---------|-----------------------------------------------------:| -| RetPostproc.FALSE | 4977 | no result post-processing generally allowed | -| RetPostproc.TRUE | 317 | but `SameUpToSelfType` requires some post-processing | - -### Newtype support - -`RetMatch.SameUpToSelfType` is a case that must be supported in some way, because supporting it -means a proper newtype support. -The number of `RetMatch.SameUpToSelfType` cases found in the collected data is also sizable -(if we didn't filter away delegation cases from `#[derive]`s it would take the first place). - -It usually corresponds to operations like this. -```rust -impl Clone for Newtype { - fn clone(&self) -> Newtype { - Newtype(self.0.clone()) +Once delegation to any kind of function is allowed, whether an inherent method, a trait method, or a free function, there is no need to restrict the parent context either. For example, `BTreeMap` implements `len` as an inherent method: + +```rust +impl BTreeSet { + pub fn len(&self) -> usize { + self.map.len() } } ``` -It's very much plausible to consider this case a delegation, but it is a case in which the return -value post-processing is necessary, but we don't want that post-processing to be written by user. -It is likely that it can be done automatically, without growing the syntax budget (like adding -result post-processing closures). -To support this case we need to figure out how to transform `Self` in return types when inheriting -signatures, and how to add the corresponding value post-processing to bodies. +and `reuse` can forward it just as easily: + +```rust +impl BTreeSet { + reuse BTreeMap::::len { self.map } +} +``` + +Note also that the parent context and the callee are independent of each other. As explained in [_Paths and callee disambiguation_](#paths-and-callee-disambiguation), you can delegate from any kind of function to any kind of function. For example, a free function can delegate to an inherent method. + +### Delegating several methods at once + +The main advantage of delegation comes with the ability to delegate multiple items at once. For example, listing `is_empty`, `clear`, and `len` as three separate `reuse` items would still be three lines whose only real difference is the method name. List delegation collapses them into one: + +```rust +impl BTreeSet { + reuse BTreeMap::::{len, is_empty, clear} { self.map } +} +``` + +Each generated method gets the receiver its callee needs: `clear` needs to mutate the map, so the method generated by `reuse` takes `&mut self`, while `len` and `is_empty` only need to read it, so those take the shared reference `&self`. The target block `{ self.map }` is the same in each case. You do not have to write the references by hand; autoref and autoderef apply according to their usual rules. + +### Renaming a delegated method + +Sometimes the callee's name isn't the name you want on your own type. `contains_key` reads naturally on a map, but for a set, `contains` is clearer. Adding `as new_name` after the callee renames the generated method: -Perhaps such post-processing can be done through a `From`-like trait that will be implemented -automatically for all newtypes, or at least will be derivable. -If the same trait is implementable manually, then we'll be able to support delegation even for -less matching return types without introducing post-processing closures. +```rust +impl BTreeSet { + reuse BTreeMap::::contains_key as contains { self.map } +} +``` -### Trait impls and coercions +You can see that the syntax of `reuse` items is generally modeled after `use` items. -If the caller is a function in a trait impl, then we can take the function signature from the trait -instead of inheriting it from the callee. -In that case implicit coercion for the return value (`RetMatch.Coerced`) will also make sense, even -if it's not very common - we generate code without any post-processing, and then type checker fills -in the coercion automatically. +### Omitting the block expression -In non-trait-impl cases signatures have to be inherited from the callee (no other place to inherit -them from), so the types will match exactly by construction and no coercions will be supported. +In the [_Paths and callee disambiguation_](#paths-and-callee-disambiguation) we used the target block `{ self }`. In such cases, it carries no information and can be omitted entirely, with the item ending in a semicolon instead: -## Non-first argument transformations +```rust +impl<'a, T> IntoIterator for &'a BTreeSet { + type Item = &'a T; + type IntoIter = Iter<'a, T>; -Non-first arguments should initially have the same type in the caller and the callee, then we -should try extending the condition to "same up to `Self` type". + reuse BTreeSet::::iter as into_iter; +} +``` -| args match | count | | -|:---------------------------|---------|---------------------------------------------------------:| -| ArgsMatch.Same | 4913 | `fn take_u8(&self, a: u8) { self.other_take_u8(a) }` | -| ArgsMatch.SameUpToSelfType | 321 | `fn foo(&self, other: &Self) { self.0.foo(&other.0) }` | -| ArgsMatch.Coerced | 60 | `fn take_vec(&self, a: &Vec) { self.take_slice(a) }` | +This is purely syntactic sugar: `reuse path;` stands for `reuse path { self }`. +Although in some cases, when the delegated function has no `self` argument, an explicit target block is not allowed, but the semicolon form will work. -| args preproc | count | | | -|:---------------------|---------|---------------------|----------------------------------------------------:| -| ArgsPreproc.No | 4987 | `arg` | no result pre-processing generally allowed | -| ArgsPreproc.Other | 226 | anything else | but `SameUpToSelfType` requires some pre-processing | -| ArgsPreproc.RefField | 39 | `&(mut) arg0.field` | | -| ArgsPreproc.Getter | 34 | `arg.get_field()` | | -| ArgsPreproc.Field | 19 | `arg.field` | | +### Methods without a receiver -### Newtype support +So far, the target block has been applied only to the callee’s receiver, while the remaining arguments (such as the `state` argument of `Hash::hash`) have been passed through unchanged. But not every forwarded method has a receiver. `Default::default` has no arguments at all: the `Default` implementation of `BTreeSet` just calls the inherent function `new`: -`ArgsMatch.SameUpToSelfType` is a case that is desirable to support for a proper newtype support. -It usually corresponds to binary operations like this. ```rust -impl Ord for Newtype { - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(&other.0) +impl Default for BTreeSet { + fn default() -> BTreeSet { + BTreeSet::new() } } ``` -It's quite plausible to consider this case a delegation, but it is a case in which argument -pre-processing is necessary, but we don't want that pre-processing to be written by user. -It is possible that it can be done automatically, without growing the syntax budget (like adding -argument pre-processing closures). -To support this case we need to figure out how to transform `Self` in argument types when inheriting -signatures, and how to add the corresponding argument pre-processing to bodies. +With delegation, the same implementation could look like this: + +```rust +impl Default for BTreeSet { + reuse BTreeSet::::new as default; +} +``` + +The target block must be omitted here because the delegated function has no parameters. + +### Delegating binary operators -Perhaps such pre-processing can be done through a `Deref`-like trait that will be implemented -automatically for all newtypes, or at least will be derivable (possibly the same one discussed in -the return type compatibility section above). -If the same trait is implementable manually, then we may be able to support delegation even for -less matching argument types without introducing pre-processing closures. +Binary operators are also different in that both operands have the same type as the receiver. Here is the forwarding implementation of the `PartialEq` trait for `BTreeSet`: -### Trait impls and coercions +```rust +impl PartialEq for BTreeSet { + fn eq(&self, other: &BTreeSet) -> bool { + self.map.eq(&other.map) + } +} +``` -If the caller is a function in a trait impl, then we can take the function signature from the trait -instead of inheriting it from the callee. -In that case implicit coercions for argument types (`ArgsMatch.Coerced`) will also make sense, even -if they are not very common - we generate code without any pre-processing, and then type checker -fills in the coercions automatically. +With delegation, the same implementation could look like this: -In non-trait-impl cases signatures have to be inherited from the callee (no other place to inherit -them from), so the types will match exactly by construction and no coercions will be supported. + +```rust +impl PartialEq for BTreeSet { + reuse PartialEq::eq { self.map } +} +``` -## Postponed features +`BTreeMap::eq` compares two maps, so the target block must be applied not only to `self`, but also to `other`. -Extending the delegation feature from functions to associated types/constants to be able to -delegate all items in a trait impl in one go, or the whole trait impl, is not proposed right now. +### Delegating methods that return the wrapper -Whole trait impl delegation is a second layer of sugar on top of the basic delegation for -individual functions, at least from implementation point of view. -You cannot delegate a whole impl without generating individual delegated functions. +The conversion also works the other way around. Here is the forwarding implementation of the `Clone` trait for `BTreeSet`: -The second layer can be supported later when the base layer is ready. -It should be useful, delegatable functions in trait impls represent 60% of all delegation cases -after all. -In our collected data about 48% of trait impls in which at least one function was delegatable -could be delegated entirely, 38% contained associated types or constants but all functions in them -could be delegated, and 14% could only be delegated partially. -We need to make sure that the selected delegation syntax can be extended to this "glob delegation" -case. +```rust +impl Clone for BTreeSet { + fn clone(&self) -> Self { + BTreeSet { map: self.map.clone() } + } -# Rejected features + fn clone_from(&mut self, source: &Self) { + self.map.clone_from(&source.map); + } +} +``` -Complex post-processing of the returned value (`RetMatch.Different`/`RetPostproc.TRUE`) is not -supported, it needs something like an output post-processing closure, which doesn't fit into the -syntax budget. +Both methods can be delegated using the list syntax shown above: -Complex pre-processing of non-first arguments (`ArgsMatch.Different`/`ArgsPreproc.Other`) is not -supported, it needs something like argument pre-processing closures, which doesn't fit into the -syntax budget. + +```rust +impl Clone for BTreeSet { + reuse Clone::{clone, clone_from} { self.map } +} +``` -Delegating to functions with a different number of arguments (`ArgsMatch.DifferentCount`) is not -supported either. +Both methods share the target block `{ self.map }`. The value returned by `Clone::clone` is wrapped, the target block is applied to the `source` argument of `Clone::clone_from`, and neither transformation has to be spelled out. -# Generated body +### Delegating a whole trait -So, the generated function body for a delegation item +`Clone` has only these two methods, so instead of listing them we can delegate all of them with a glob: + ```rust -#[attrs] -pub(vis) delegation_keyword name as rename { target_expr_template } +impl Clone for BTreeSet { + reuse Clone::* { self.map } +} ``` -will look like this (the first argument may be `self`). +A glob delegation item behaves as if all the methods of the trait were listed, including those with default implementations. `clone_from` is such a method. A glob delegation can also be written as follows: + + ```rust -#[attrs] -pub(vis) fn rename(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { - resolved_callee(target_expr_template(arg0), arg1, ..., argN) +reuse impl Clone for BTreeSet { self.map } +``` + +This form is purely syntactic sugar for the previous form. + +## Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +### Syntax + +This proposal introduces two new [item kinds](https://doc.rust-lang.org/reference/items.html), function delegation items and impl delegation items: + +```diff +Item → + OuterAttribute* ( VisItem | MacroItem ) + + VisItem → + Visibility + ( + Module + | ExternCrate + ... ++ | FnDelegation ++ | ImplDelegation +``` + +From here on, function delegation items are referred to simply as "delegation items". + +Delegation items are accepted syntactically and semantically in all contexts where functions with bodies are accepted semantically. +That means modules and blocks, traits, and implementations, but not `extern` blocks. In `extern` blocks, delegation items are rejected syntactically ([?](#why-can-delegation-items-be-declared-in-any-position)). Like other items, delegation items may be annotated with a visibility modifier ([?](#why-is-visibility-manually-added-instead-of-being-copied-from-the-callee)) and may have attributes applied to them ([?](#why-are-attributes-manually-added-instead-of-being-copied-from-the-callee)). + +Delegation items have the form: +```diff ++ FnDelegation → ++ reuse DelegationPaths ( BlockExpressionNoInnerAttributes | ; ) ++ ++ DelegationPaths → ++ PathExpression ( as IDENTIFIER )? ++ ( PathExpression | QualifiedPathType ) :: { ( PathIdentSegment ( as IDENTIFIER )? )* , ? } ++ ( PathExpression | QualifiedPathType ) :: * +``` + +The grammar is generally modeled after `use` items, with two major differences: qualified paths and generic arguments in paths are supported, and nested lists and globs are not supported. + +A delegation item starts with the `reuse` keyword ([?](#why-reuse)) and consists of a path prefix, which may be either simple or qualified, a suffix, and an optional block expression. Their roles are discussed in the following sections. + +Suffixes come in three flavors: individual delegation, list delegation ([?](#why-is-list-delegation-supported)), and glob delegation ([?](#why-is-glob-delegation-supported)). The optional `as IDENTIFIER` clause allows the delegated function to be defined with a different name ([?](#why-is-renaming-supported)). + +A delegation item intentionally does not provide syntax for introducing its own generics ([?](#why-doesnt-a-delegation-item-provide-syntax-for-introducing-its-own-generics)). A delegation item intentionally does not provide syntax for argument or return-value transformations, besides the target block ([?](#why-doesnt-a-delegation-item-provide-syntax-for-argument-or-return-value-transformations)). + +> [!NOTE] +> +> Expression path syntax (with mandatory turbofish) is used for consistency with other value paths, but it is not technically necessary. Type path syntax (with optional turbofish) could be supported later if necessary. + +Impl delegation items have the form: +```diff ++ ImplDelegation -> ++ `reuse` `unsafe`? `impl` GenericParams? `!`? TypePath `for` Type ++ WhereClause? ++ ( BlockExpression | ; ) +``` + +This is the same syntax as for regular `impl` items, except that the block containing associated items is replaced with a block expression. +Impl delegation items are accepted in all contexts where regular impl are accepted. + +### List, glob, and impl delegation + +List, glob, and impl delegations are three kinds of higher-level syntactic sugar that expand to individual function delegations at macro expansion time. + +#### List delegation + +List delegation defines several items at once from a shared path prefix. It desugars to one individual delegation item per name. + +Target blocks, generic arguments, and other components are copied at the token-stream level, making list delegation a macro feature. + +```rust +reuse prefix::::{a, b, c} { target } +``` +expands to +```rust +reuse prefix::::a { target } +reuse prefix::::b { target } +reuse prefix::::c { target } +``` + +If a target block or a generic argument contains something with an identity, such as an item or a closure, it is also copied as tokens. As a result, multiple distinct, independent items or closures are created ([extended rationale](https://github.com/rust-lang/rfcs/pull/3530#issuecomment-2020869823)). + + +```rust +reuse prefix::{a, b} { + use some::import; // import + self.field.map(|x| x.y) // closure +} + +// Desugars to +reuse prefix::a { + use some::import; // import 1 + self.field.map(|x| x.y) // closure 1 +} +reuse prefix::b { + use some::import; // import 2 + self.field.map(|x| x.y) // closure 2 +} +``` + +Empty list delegations are currently prohibited (see [_Future possibilities: Empty list delegation_](#empty-list-delegation)). + +#### Glob delegation + +Glob delegation allows all methods of a trait to be delegated at once. It desugars to one individual delegation item per "glob-imported" name. + +Glob delegations are semantically allowed only inside implementations, and the path prefixes in glob delegations can only refer to traits ([?](#why-are-glob-delegations-restricted)). Note that there are no similar restrictions on list delegations. + +The set of names for which individual delegation items are produced is determined as follows: +- The full set of names defined by the target trait in all namespaces is considered. +- Names already explicitly defined inside the glob delegation's parent context (the impl) are filtered out ([?](#why-can-names-from-glob-delegations-be-overridden-by-explicit-items)). "Explicitly" here means not by another glob delegation. +- If any of the remaining names refers to an associated type or constant, an error is reported for future compatibility with associated type and const delegation (see [_Future possibilities: Support for delegating types and consts_](#support-for-delegating-types-and-consts)). +- Note: the above rules mean that a glob delegation can only be expanded after all macro invocations in its target trait and its parent impl have been expanded, except perhaps other glob delegations. + +Note that individual delegations are still generated for functions with default bodies in the target trait definition. +This ensures that manual implementations of functions with default bodies are correctly forwarded to ([?](#why-are-methods-with-default-bodies-included-in-glob-delegation)). + +As with list delegations, target blocks, generic arguments, and other components are copied at the token-stream level, making glob delegation a macro feature. + +Empty glob delegations are currently prohibited (see [_Future possibilities: Empty list delegation_](#empty-list-delegation)). + +
+ + Example: desugaring of glob delegation. + + +```rust +trait Trait { + fn a() {} // has default body + fn b(); + fn c(); +} +impl Trait for Type { + fn c() {} // explicitly defined name + reuse Trait::::* { target } +} +``` +expands to +```rust +impl Trait for Type { + fn c() {} // explicitly defined name, not delegated + reuse prefix::::a { target } // delegated, despite the default body + reuse prefix::::b { target } } ``` -How exactly the `resolved_callee` is obtained is a large separate question, it is discussed below. -Substitution of the `target_expr_template` is also discussed below. +
-The code generation here is supposed to be "macro-like", that means we generate the code above -(at some high enough IR level), but it is possible that it won't type check and will report errors. -The type checking may also insert implicit code like auto-refs, auto-derefs, or coercions. -The generation cannot happen literally at macro expansion level though, because it needs to inherit -the signature part from the callee (or from some trait), and signatures are not yet ready this -early. +#### Impl delegation -Support for the `SameUpToSelfType` case (limited automatable pre-processing for non-first arguments -and post-processing for the output) is not yet added to this example. +Impl delegation is a second layer of syntactic sugar that makes it convenient to write a trait impl containing a glob delegation to the same trait. -# Implementation and design details + +```rust +reuse impl Trait for Type { target } +``` +expands to +```rust +impl Trait for Type { + reuse Trait::::* { target }; +} +``` -## How the signature is inherited +All restrictions on regular glob delegations also apply to glob delegations produced by impl delegations. + +### Paths and name resolution + +Delegation reuses the existing path mechanism and does not introduce a new approach to name resolution. +Paths allow delegation items to unambiguously identify callable items they forward to, including trait methods, trait implementation methods (with qualified paths used to specify `Self`), inherent methods, and free functions ([?](#why-are-qualified-paths-used-for-call-disambiguation)). Also see [_Future possibilities: Name-based resolution as sugar_](#name-based-resolution-as-sugar). + +Delegation items can also refer to other delegation items. If a cycle is encountered in such a chain of recursive delegations, an error is reported. + +Delegation paths are resolved in the value namespace, and if the path doesn't refer to a function or associated function, an error is reported. +In particular, delegation for associated types and constants is not currently supported (see [_Future possibilities: Support for delegating types and consts_](#support-for-delegating-types-and-consts)). + +Type-relative paths are also supported, although support for them on nightly rustc is currently limited. + +> [!NOTE] +> +> Delegation to inherent methods is particularly complex to implement. From the perspective of name resolution, paths in Rust may be classified as follows: +> - A path to a free function (e.g., `module::func`). +> - A reference to an associated item defined in a trait (e.g., ` as Clone>::clone`), where the `Self` type may also be omitted. +> - A type-relative path (e.g., `::default`). +> +> Lowering a delegation item into a real function requires knowing the callee's signature, including its generics, the number of arguments, and whether and how it takes a `self` argument. With this information, a signature can be synthesized for the new item. Paths in the first two categories can be resolved early enough to expose that information. Type-relative paths generally cannot: their resolution is not known until type checking, by which point the delegation item's signature is already needed. +> +> Currently, to work around this limitation, we use simple name-based resolution during [AST lowering](https://rustc-dev-guide.rust-lang.org/hir/lowering.html): we only resolve to an inherent method with the specified name. This does not work in more complex cases, such as when a type-relative path resolves to a trait method: +> +> ```rust +> pub struct Struct {} +> +> trait Trait { +> fn to_string(x: &Struct) -> String { +> // default impl +> } +> } +> +> impl Trait for Struct {} +> +> impl Struct { +> pub fn get_string(x: &Struct) -> String { +> Struct::to_string(f) +> } +> } +> ``` +> +> `Struct::to_string` resolves to `Trait::to_string`. However, since we cannot perform trait selection during lowering, we report an error. This limitation could potentially be addressed in the future, the current workaround is to use `Trait::to_string`. See [_Future possibilities: Supporting type-relative paths_](#supporting-type-relative-paths). + +### Desugaring of individual delegation: signature + +All other delegation forms desugar into individual delegations as specified above. Here, we describe how individual delegations desugar further into regular functions. + +Individual delegation defines exactly one new function item that forwards to exactly one callee identified by a path. + +The function from which the delegated item's signature is copied is called the _delegation resolution_. +For delegations defined in a trait implementation, the delegation resolution is the corresponding trait method ([?](#why-is-the-delegation-resolution-the-corresponding-trait-method-in-trait-implementations)). In all other cases, it is the item resolved by the path (see [_Paths and name resolution_](#paths-and-name-resolution) for details on how the path is resolved). + +The generated function header for an individual delegation has the following form: + + +```rust +#[attrs] +pub(vis) reuse path as name { target_expr } +``` +desugars to +```rust +#[attrs] +pub(vis) FunctionQualifiers fn name(..., argN: ArgN, ...) -> FunctionReturnType +WhereClause +{ + /* body */ +} +``` -### Trait impls, refinement, and coercions +- Outer attributes (`#[attrs]`) are exactly those specified by the user at the delegation site, if any, plus [_default attributes_](#default-attributes). +- Visibility (`pub(vis)`) is exactly as specified by the user at the delegation site. Also see [_Unresolved questions: Should the visibility of the delegation item be restricted?_](#should-the-visibility-of-the-delegation-item-be-restricted) +- Function qualifiers (`FunctionQualifiers`: safety, constness, asyncness, and ABI) are copied unchanged from the delegation resolution. + - None of these qualifiers can be overridden ([?](#why-are-function-qualifiers-copied-unchanged)). +- If the delegation item has an `as name` clause, then `name` is used as the generated function's name; otherwise, the final segment of `path` is used. +- Generic parameters (`GenericParams`) and where clauses (`WhereClause`) are copied from the delegation resolution and remapped as described in [_Generics remapping_](#generics-remapping). +- Function parameters (e.g., `argN: ArgN`) are copied from the delegation resolution: + - Generic parameters appearing in the function arguments are remapped as described in [_Generics remapping_](#generics-remapping). + - The `self` parameter turns into a regular first parameter if the parent context is not an impl or trait. + - Delegating to C-variadic functions is not supported ([?](#why-is-delegation-of-variadic-functions-not-supported)). +- The return type (`FunctionReturnType`) is copied from the delegation resolution: + - Generic parameters appearing in the return type are remapped as described in [_Generics remapping_](#generics-remapping). -If our delegation item is in a trait impl `impl Trait for Type { /*delegate foo*/ }` we have two -opportunities: -- Inherit the signature from `Trait::foo`. -- Inherit the signature from the callee, e.g. `SomeFieldType::foo`. +> [!NOTE] +> +> Desugaring happens mainly during [AST lowering](https://rustc-dev-guide.rust-lang.org/hir/lowering.html). This is because once [HIR](https://rustc-dev-guide.rust-lang.org/hir.html) construction is complete, the crate becomes immutable and code modification is no longer possible at that stage. The types and generics are then processed further during HIR -> Ty lowering. -Right now the signature is always required to match the trait anyway, so there's not much -difference, an error will be reported if the inherited signature doesn't match the trait. +#### Default attributes -However, with the ["Refined trait implementations" RFC](https://github.com/rust-lang/rfcs/pull/3245) -accepted the situation becomes more complex. -Signature inherited from the callee may be different from the trait, but that could still be fine -due to refinement. +An `#[inline]` attribute is implicitly added to the generated function unless the delegation item already has an `inline` attribute. -- Inheriting signature from the trait means not supporting refinements. -- Inheriting signature from the callee means not supporting coercions -(`RetMatch.Coerced`, `ArgsMatch.Coerced`, see above). +A `#[must_use]` attribute is implicitly copied to the generated function if the delegation resolution has one, unless the delegation item already has a `must_use` attribute (perhaps with a different message). +It is not currently possible to make the generated function non-`must_use` if the delegation resolution is `must_use`. -The `#[refine]` attribute proposed by RFC 3245 could potentially be used for changing the behavior -from one to another. We suggest inheriting signatures from the trait by default. +Also see [_Unresolved questions: Which attributes should be added by default?_](#which-attributes-should-be-added-by-default) -If our delegation item has any other parent than a trait impl, then we have no other choice than to -inherit the signature from the callee. +#### Path elaboration -### `Self` mapping +Generic arguments in the delegation path are "elaborated" in the same way as in other paths in expression contexts. +This means that generic arguments not specified explicitly are filled in with their defaults, filled in with `'static` for elided lifetimes, or replaced with inference placeholders (`_` for types or `'_` for lifetimes), depending on the context. -If the signature is inherited from the callee, then we need to change the callee's `Self` type to -the caller's `Self` type when copying the signature. +The generics remapping process below uses generic arguments from delegation paths in their elaborated form. -This needs to happen for the `self` parameter at least, but for other parameters and return type -as well if we aim to support the `SameUpToSelfType` cases. +#### Generics remapping -### `self` parameter +As mentioned earlier, a delegation item cannot explicitly introduce its generic parameters. Instead, they are copied from the delegation resolution. -Whether the first parameter of a function is `self` is a part of signature (or at least part of -public interface), because only functions with `self` can be called using method call syntax. -Multiple cases are possible when inheriting `self`-ness of the first parameter from the callee. +However, after copying, we need to remap the generics so that the copied signature and where clauses remain semantically equivalent to what would be written by hand (e.g., the callee's parent parameters don't automatically make sense once copied into a different scope). -- If we are in a trait impl and the trait requires `self`, then `self` it is. -- If we are in a trait impl and the trait requires no `self`, then a regular non-self parameter is -used. -- If we are in an inherent impl or a trait, then either `self` or a regular parameter, depending on -whether the callee's `arg0` is `self`. -- If we are in a free function, then a regular parameter is used. +The delegation resolution's signature may contain: +- Own generic parameters - generic parameters defined directly by the delegation resolution item. +- Parent generic parameters, including `Self` - generic parameters defined by the delegation resolution's parent trait or impl. + - After copying, all parent parameters start as *unsubstituted*. Replacing the parameter's uses with the provided arguments makes it substituted. -Alternatively, we just could report `self`-ness mismatches as errors. +The following procedure is used for remapping each own parameter: +- The generic argument corresponding to the parameter is identified in the last segment of the elaborated callee path. +- If the generic argument is an inference placeholder (`_` or `'_`), then both the generic parameter definition and its uses stay in place ([?](#why-are-inference-placeholders-allowed-in-paths)). + - Nested inference placeholders are not allowed ([?](#why-are-nested-inference-placeholders-not-allowed-in-paths)). +- If the generic argument is not an inference placeholder, then the generic parameter's definition is eliminated from the generated function and all its uses are replaced with that argument ([?](#why-might-own-generic-parameters-need-to-be-substituted)). -Note, that the `self`'s *type* is always inherited (modulo `Self` mapping), regardless of the -`self`-ness property (except perhaps in cases when we are in a trait impl and the signature is -inherited from the trait). That is required to inherit complex self types like `Pin` correctly. +The following procedure is used for remapping the `Self` parent parameter: +- If the parent context is an impl or a trait, then all the parameter's uses are replaced with the impl's or trait's self type. + - The `Self` argument in the callee path is ignored, even if it is specified ([?](#why-is-the-self-type-not-substituted)). +- Otherwise, the generic argument corresponding to the `Self` parameter is identified in the elaborated qualified callee path. +- If the generic argument is an inference placeholder, then uses of `Self` stay in place and remain unsubstituted. + - Nested inference placeholders are not allowed. +- If the generic argument is not an inference placeholder, then all uses of the parameter are replaced with that argument. -### Open questions +The following procedure is used for remapping each non-`Self` parent parameter ([?](#why-might-parent-parameters-need-to-be-substituted)): +- If the parent context is a trait impl (`impl Trait for ...`), then all the parameter's uses are replaced with the corresponding argument in `Args`. + - Any matching arguments in the callee path are ignored. + - This is because the generated signature must match the corresponding trait method, while the delegation path may refer to a different item whose generic parameters do not necessarily correspond to those of the trait method. +- Otherwise, if the callee's parent is a trait, the generic argument corresponding to the parameter is identified in the trait segment of the elaborated callee path. +- Otherwise, if the callee's parent is an inherent impl, the corresponding parameter value is determined from results of the type-relative path resolution. + - When a type-relative path `Type::assoc` is resolved, it is tied to a specific impl block `impl Type` (the callee's parent) with a specific list of substitutions for `ImplParams`, even if that the `ImplParams` list does not *directly* correspond to the `TypeArgs` list. If the substitution is the parameter definition itself, then we treat is as an inference placeholder below. +- If the generic argument is an inference placeholder, then uses of the parameter stay in place and remain unsubstituted. + - Nested inference placeholders are not allowed. +- If the generic argument is not an inference placeholder, then all uses of the parameter are replaced with that argument. -Signature inheritance is a source of a few questions that we cannot answer right now, and that will -need to be answered during implementation. +If any parent parameters in the signature or where clauses remain unsubstituted, an error is reported ([?](#what-happens-if-unsubstituted-parent-parameters-remain-after-substitution)). -- In particular, how are predicates inherited? - - If the callee signature refers to generic parameters defined by the callee's parent item, what do -we do with them exactly? +
-- Are signatures to inherit available early enough? - - Ideally we would generate delegation items as HIR, but it is not possible at the moment - all HIR -is generated at once and we cannot add more HIR later. -Generating HIR per-item is probably possible, and it's a goal for people working on incremental -compilation, but it may require some larger infrastructure changes in the compiler. - - Are the signatures available at the astconv time at least? - - If the callee comes from a trait impl `impl Trait for Type { fn foo() }`, then can we take the -`foo`'s signature from that specific impl, rather than from the `Trait`, early enough? -That would mean supporting trait impl refinements on the callee side. - - Passes like variance inference are global and require all signatures to be available at once. -Can delegated items be exempted from this global pass and variance-checked later? -Can it cause infinite cycles with delegation item requiring some other items' signature, and that -other item in its turn requiring the delegation item's signature? + Example: generics remapping with step-by-step desugaring. -- Specific mechanism of the `Self` mapping discussed above is not yet clear an needs to also be -figured out during implementation. +Consider this example, in which an inherent impl delegates to a trait: +```rust +trait Trait { + fn method(&self, arg1: &T, arg2: &U); +} -This all will likely need input from people from the types team, and people who implemented -`impl Trait`, which encountered similar issues. +impl Wrapper { + reuse Trait::method { self.inner } +} -## How `resolved_callee` is determined +``` -We should support delegation in two flavors, that differ in the way they determine the callee -function. +Step 0, immediately after desugaring but before remapping, looks like this: +Copied but not yet substituted parameters (`T`) are written as `?T`, the same notation is used in the rationale sections as well. -With one flavor the callee path would be specified explicitly, and with another it would be -inferred from `target_expr_template`. -These two flavors can be expressed like this. ```rust -// Explicit paths -delegation_keyword module::name { target_expr_template } -delegation_keyword Type::name { target_expr_template } -delegation_keyword ::name { target_expr_template } +impl Wrapper { + fn method(self: &?Self, arg1: &?T, arg2: &?U) { + Trait::<_>::method::<_>(&self.inner, arg1, arg2) + } +} +``` + +`?U` is one of the function's own parameters. It is copied together with its scope (`method`) as part of the function itself, so it can always be substituted with its new definition if necessary. +`?T` and `?Self` were defined in the parent scope, so immediately after copying, they are unsubstituted. -// Inferred from `target_expr_template` -delegation_keyword name { target_expr_template } +After remapping, this becomes: +```rust +impl Wrapper { + fn method(self: &Wrapper, arg1: &?T, arg2: &U) { + Trait::<_>::method::<_>(&self.inner, arg1, arg2) + } +} ``` -Doing this makes sense at least from the implementation staging point of view, but it likely makes -sense from the language point of view as well, because in some cases the callee cannot be inferred -from `target_expr_template` (that includes any delegation to free functions in particular, but -that's not the only case). +Thus, `?T` remains unsubstituted, and this will produce an error. +However, if we wrote `Trait::::method` instead of just `Trait::method`, then `?T` would be substituted with `u8` and the code would be legal. + +
+ +Also see [_Future possibilities: More sophisticated inference of generic parameters_](#more-sophisticated-inference-of-generic-parameters) -### Callee is specified explicitly +#### Effective self type identification -This case is simpler for the compiler. -Path to the callee is known, so it is just used in the generated body. +To increase the usefulness of delegation and provide better support for newtypes, we need to identify types that are "actually `Self`" in method signatures, not just for the `self` parameter but also for other parameters and the return type ([?](#why-are-effective-self-types-identified-beyond-the-receiver)). + +For example, the occurrences of `Struct` in `other: Struct` and `-> Struct` in the following impl are "actually `Self`". ```rust -fn name(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { - callee_path(target_expr_template(arg0), arg1, ..., argN) +trait Trait { + fn method(self, other: Self) -> Self; +} + +impl Trait for Struct { + fn method(self, other: Struct) -> Struct { unimplemented!() } } ``` +Let's call such types in signatures "effective self types". + +The following procedure is used for detecting effective self types. -### Callee is inferred from `target_expr_template` +If the delegation resolution is a trait method, then the `Self` parameter occurrences in that trait method (before substitution) are considered effective self types in the generated function. +In situations like +```rust +trait BinOp { + fn bin_op(&self, rhs: &Rhs); +} +``` +we also need to treat `Rhs` as an effective self type if it was obtained from the `Self` parameter default; otherwise, newtype conversions won't work correctly when delegating standard binary operators. -#### The proposed algorithm +If the delegation resolution is an inherent method with `self`, then its corresponding type is considered an effective self type in the generated function. -- We take the expression `target_expr_template.name()` and turn it into a body for type checking. -- Every `self` in it is assigned the type `Self` and treated as `value_of::()`, i.e. as -"`self` by value". - - That means target expressions involving `*self` are not supported for callee inference -(unless `Self` itself implements `Deref`). Inferring callees having `Pin` in their `self` will -seemingly not work either. Explicit paths will need to be used in these situations. -- Then the body is type checked with the sole purpose of determining the resolution of `name`. - - The resolution doesn't depend on arguments passed to the `.name()` call, or even on their number, -so we can resolve such method call without passing any arguments. - - I think we can resolve static methods this way too, right now `rustc` does it for diagnostics -(if the resolved method doesn't have `self` then an error is reported), but we should be able to -guarantee the precise behavior here too. -This is useful if we want to delegate a list of both static and non-static methods in one go (or to -"glob delegate" the whole trait impl, see postponed features), with inference from -`target_expr_template`. -- Once we have the callee resolution this body is thrown away, type checking results from this -temporary body are not reused in any way when we start type checking the generated code. +In all other cases, types in signatures are not considered effective self types. In particular, effective self types are *not* detected by tracking uses of the `Self` type alias in impls (as opposed to the `Self` parameter in traits) or by checking type equality ([?](#why-are-effective-self-types-not-detected-through-type-aliases-or-type-equality)). -The body is then generated in the same way as with explicit paths, and type checked again. ```rust -fn name(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { - resolved_callee(target_expr_template(arg0), arg1, ..., argN) +impl Struct { + fn method(self, other1: Self, other2: Struct) {} } + +// Types of `other1` and `other2` are not considered effective self types here. +reuse Struct::method; ``` -The only difference is that adjustments on `target_expr_template(arg0)` should be added as if it -was a method receiver, rather than a regular call argument. Otherwise you may have to write -something like `&self.field` in your `target_expr_template` instead of just `self.field`, which is -not good. -#### The questionable alternative +If a function parameter's type is an effective self type, possibly wrapped in one of the references or smart pointers mentioned in [items.associated.fn.method.self-ty](https://doc.rust-lang.org/reference/items/associated-items.html#r-items.associated.fn.method.self-ty), then let's call it an "effective self parameter" ([?](#why-are-specific-smart-pointers-used-for-detecting-effective-self-parameters)). +If the function's return type is an effective self type, without any additional wrapping, let's call it a "self return type" ([?](#why-are-self-return-types-limited-to-bare-self-type)). + +In the generated function body, effective self parameters are converted using the delegation's target block, and self return types are converted using newtype wrapping. +See the [_body desugaring chapter_](#desugaring-of-individual-delegation-body) for details. + +### Desugaring of individual delegation: body + +#### Target block + +The target block is an optional [block expression](https://doc.rust-lang.org/beta/reference/expressions/block-expr.html) ([?](#why-is-the-target-block-a-block-expression)) that is used to transform the delegation item's effective self parameters before they are forwarded to the resolved callee. There are no restrictions on the expressions that can be used inside the target block ([?](#why-is-the-target-block-unrestricted)). + +Inside that block, `self` refers to an effective self parameter that will be transformed. +If the generated function has no effective self parameters, then it's an error to specify the target block, unless the current delegation item was defined as part of a list or glob delegation in which at least one other item has an effective self parameter ([?](#why-can-target-blocks-be-allowed-without-effective-self-parameters)). + +If the target block is omitted, then no parameter transformations happen, and delegated functions without effective self parameters also work. -Alternatively, we could generate and type check the body like this +The target block contains zero or more statements and an optional trailing expression, called the target expression. +If the trailing expression is omitted, then `self` is implicitly used as the trailing expression ([?](#why-is-self-the-implicit-target-expression)). + +#### Body desugaring + +Suppose that the generated function has `N` effective self parameters. Then the target block is "disassembled" and its statements and trailing expression are inserted into the generated body `N` times, as shown in the following example ([?](#why-are-statements-not-passed-to-the-call)). + + ```rust -fn name(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { - target_expr_template(arg0).name(arg1, ..., argN) +// target block +{ + target_stmt_1(self); + ... + target_stmt_M(self); + target_expr(self) +} + +// generated body, param1..paramN are the effective self parameters +(..., param1: Type1, ..., paramN: TypeN) { + target_stmt_1(param1); + ... + target_expr_M(param1); + ... + target_stmt_1(paramN); + .... + target_expr_M(paramN); + callee_path(..., ADJ(target_expr(param1)), ..., ADJ(target_expr(paramN)), ...) } ``` -, but then `name` could actually resolve to a different function `resolved_callee2`! -(Given smart enough combination of `target_expr_template` and callee signature). -That doesn't seem good, given that the signature is still inherited from the first -`resolved_callee`. -#### What if the inferred callee is a static method? +Under these rules, statements with side effects (e.g., `dbg!(&self);`) execute once per effective self parameter. + +
+ + Example: debug logging any effective self parameter -Another question arises if `resolved_callee` turns out to be a static method. -A simple example: ```rust -// Delegation -impl Newtype { delegation_keyword static_method { self.field } } +trait BinOpLike { + fn bin_op(&self, rhs: &Self); +} + +reuse impl BinOpLike for Wrapper { + dbg!(self); + self.inner +} -// Generated body -fn static_method(arg: u32) { - FieldType::static_method(arg.field) +// Desugaring +impl BinOpLike for Wrapper { + fn bin_op(&self, rhs: &Wrapper) { + dbg!(self); + dbg!(rhs); + BinOpLike::bin_op(self.inner, rhs.inner) + } } ``` -But that's typically not what we want at all! -`self.field` here is used only for inferring the callee, but in the generated body we want to throw -it away and generate this instead. + +
+ +`ADJ` denotes the same set of adjustments as for an ordinary [method call](https://doc.rust-lang.org/reference/expressions/method-call-expr.html) receiver: a sequence of autoderefs, an optional autoref, and coercions ([?](#why-are-method-call-adjustments-applied-to-effective-self-parameters)). The difference is that the callee has already been resolved through the path, so these adjustments are not needed for method resolution. Instead, they are applied to the arguments to make them match the callee's signature. + +
+ + Example: Automatic adjustments, glob and list delegations + ```rust -fn from_u32(arg: u32) { - FieldType::static_method(arg) +trait Trait { + fn static_f(); + fn by_value(self); + fn by_ref(&self); + fn by_mut_ref(&mut self); +} + +struct X(T); +impl X { + // List delegation. + // Target expression is automatically adjusted for each function + // and removed for function without receiver. + reuse ::{static_f, by_value as new_name} { self.0 } +} + +struct X2(T); +impl X2 { + // Glob delegation. + // Target expression is automatically adjusted for each function + // and removed for function without receiver. + reuse ::* { self.0 } } ``` -The solution is to generate a different body if the combination of these two factors happen: -- Delegation flavor with callee inference is chosen -- The inferred callee doesn't actually have a `self` parameter +
+ +All remaining parameters, which are not effective self parameters, are forwarded unmodified. Method-call adjustments are not applied to them, although implicit coercions may still apply. + +The path `callee_path` is exactly as specified by the user, except that the generated function's own generic parameters are substituted as arguments to the final segment, rather than left to inference ([?](#why-are-the-delegation-resolutions-own-generic-parameters-substituted-as-arguments-to-the-final-segment)). + +In a trait implementation, the delegation resolution and the actual callee may be different functions. +If any of the transformed or untransformed parameters are incompatible with the callee's signature, a type-checking error will be reported. + +#### Return type wrapping + +If the generated function's return type is a self return type, then the `callee_path` expression is additionally wrapped in a struct literal to perform "newtype wrapping". + +```rust +Self { _: callee_path(...) } +``` + +`_` in this case denotes the single field of `Self` ([?](#why-is-return-type-wrapping-restricted-to-single-field-structs)). +If `Self` is not a struct (or union) with a single field, then an error will be reported. +An error will also be reported if that field is inaccessible from the delegation's definition site (e.g., because of its visibility). + +
+ + Example: Self type identification and wrapping of the return value -Then we generate the alternative body instead ```rust -fn name(arg0: Arg0, arg1: Arg1, ..., argN: ArgN) { - resolved_callee(arg0, arg1, ..., argN) +trait MyAdd { + fn add(self, other: Self) -> Self; +} + +impl MyAdd for usize { + fn add(self, other: usize) -> usize { self + other } +} + +struct W(usize); +reuse impl MyAdd for W { self.0 } + +// Desugaring: +fn add(self: W, arg1: W) -> W { + // We detect that arguments are of type `Self` so we + // apply target expression and adjustments to all of them, + // next we wrap return value into a newtype. + W { 0: MyAdd::add(self.0, self.0) } } ``` -, with `target_expr_template` being thrown away. -It's hard to estimate how common this pattern will be by collecting data from existing code, -because `expr.static_method()` doesn't currently compile and produces and error. -I don't think this case breaks the general rule (`arg0` is transformed using `target_expr_template`), it -rather extends it. -If you need to delegate to a static method, but still transform `arg0`, then the delegation flavor -with explicit path can be used instead. -`arg0` does get transformed occasionally when delegating to functions without `self` -| arg0 preproc | count | -|:-------------------|--------:| -| Arg0Preproc.No | 879 | -| Arg0Preproc.Other | 47 | -| Arg0Preproc.Getter | 7 | +
+ +## Drawbacks +[drawbacks]: #drawbacks + +Many cases of delegation require more than simple forwarding (e.g., transforming arguments or return values). This feature only handles the simple cases, leaving complex transformations to manual coding or macros. This might limit the feature's usefulness, but the rationale for this is given in the [_syntax budget section_](#rule-1-stay-within-the-syntax-budget). + +The delegation feature could potentially be implemented as a third-party library with compile‑time [_reflection_](#reflection) (if and when that becomes available). + +TODO: collect remaining drawbacks from the rationale sections. + +## Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +### Design guiding principles + +A recurring question throughout this RFC is whether a particular delegation pattern should be supported. The following guiding principles inform individual design decisions. + +### Rule 1: stay within the syntax budget + +Delegation items should use syntax that is no more complex than that of `use` imports. + + +```rust +// Import item +#[attrs] +pub(vis) use prefix::{a, b, c as d}; + +// Delegation item +#[attrs] +pub(vis) reuse prefix::{a, b, c as d} { target_expr } +``` + +The motivation here is to avoid more complex features such as argument or return-value transformations, which would require preprocessing or postprocessing closures. With additional bells and whistles like that, a delegation item becomes as verbose as a full forwarding function implementation, but less readable. These transformations can instead be written manually or expressed using a macro (see [_Prior art_](#prior-art)). + +### Rule 2: prefer generality over special casing + +If a pattern fits within the proposal's syntax budget and can be expressed by a single, uniform desugaring rule, support it, even when it is expected to be rare in practice, rather than limit the support to what appears to be the common case. + +This approach allows us to explore the design space more thoroughly, as discussed in the [_Motivation_](#generalization) section. + +This is a default, not an absolute rule; exceptions may be made when there is a sufficiently strong reason to do so. + +### Design decisions outlined in this RFC + +#### Why can delegation items be declared in any position? + +Delegation fundamentally forwards function calls. A regular function in Rust may be a trait method, a method in a trait implementation, an inherent method, or a free function. We can form different combinations based on the position of a caller and a callee: + +
+ + Example: delegating from a trait implementation to an implementation of the same trait. + +[example link](https://github.com/rust-lang/rust/blob/752b9bf8798c2ffc1d3fe2b804c04454366fc6d6/library/alloc/src/string.rs#L3635-L3641) + +```rust +pub struct Drain<'a> { + iter: Chars<'a>, +} + +impl Iterator for Drain<'_> { + type Item = char; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next() + } +} +``` + +
+ +
+ Example: delegating from a trait implementation to an implementation of another trait. + +[example link](https://github.com/rust-lang/rust/blob/752b9bf8798c2ffc1d3fe2b804c04454366fc6d6/library/core/src/iter/adapters/zip.rs#L74-L84) + +```rust +trait ZipImpl { + type Item; + fn next(&mut self) -> Option; +} + +impl Iterator for Zip +where + A: Iterator, + B: Iterator, +{ + type Item = (A::Item, B::Item); + + #[inline] + fn next(&mut self) -> Option { + ZipImpl::next(self) + } +} +``` + +
+ +
+ Example: delegating from an inherent method to a trait implementation. + +[example link](https://github.com/rust-lang/rust/blob/752b9bf8798c2ffc1d3fe2b804c04454366fc6d6/library/std/src/collections/hash/set.rs#L149-L151) + +```rust +impl HashSet { + pub fn new() -> HashSet { + Default::default() + } +} +``` + +
+ +
+ Example: delegating from a free function to an inherent method. + +[example link](https://github.com/rust-lang/rust/blob/752b9bf8798c2ffc1d3fe2b804c04454366fc6d6/compiler/rustc_ast_pretty/src/pprust/mod.rs#L99-L101) + +```rust +pub fn to_string(f: impl FnOnce(&mut State<'_>)) -> String { + State::to_string(f) +} +``` + +
+ +etc. + +All these combinations appear in real-world code through regular calls, and each represents a potential use case for the delegation feature. Choosing which combinations to support is a design decision driven by multiple factors: the function call resolution algorithm, the available syntax budget, the frequency of the use case, and the extensibility to other cases. + +Generality is particularly relevant in light of the existing prior art. The two previous delegation RFCs, [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406) and [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393), deliberately limited delegation to trait methods. Other proposals like [rust-lang/rfcs2375](https://github.com/rust-lang/rfcs/pull/2375) and [rust-lang/rfcs#3591](https://github.com/rust-lang/rfcs/pull/3591) address other use cases through different language mechanisms. + + +As established in the name resolution section, the callee may resolve to any of these kinds of functions. We see no reason to restrict the caller either (see [_guiding principles_](#design-guiding-principles)). Accordingly, this proposal supports every combination, rather than special-casing only the most common ones. + +↩ [_Syntax_](#syntax) + +#### Why is visibility manually added instead of being copied from the callee? + +A delegation item is a distinct item whose behavior may deliberately differ from that of its callee. This also avoids ambiguity for users about whether omitting a visibility modifier makes the delegation item private or causes it to inherit the callee's visibility. + +Also see [_Unresolved questions: Should the visibility of the delegation item be restricted?_](#should-the-visibility-of-the-delegation-item-be-restricted) + +↩ [_Syntax_](#syntax) + +#### Why are attributes manually added instead of being copied from the callee? + +Attributes may affect diagnostics, linking, documentation, or the item's public API contract. A delegation item is a distinct item whose behavior may deliberately differ from that of its callee. Automatically inheriting attributes would also mean a delegation item's behavior could change silently whenever the callee's attributes change, with no corresponding edit at the delegation site. + +Also see [_Unresolved questions: Which attributes should be added by default?_](#which-attributes-should-be-added-by-default) + +↩ [_Syntax_](#syntax) + +#### Why `reuse`? + +The delegation syntax is generally modeled after `use` items to make it familiar and keep it within the syntax budget. +The keyword is similar to `use` for the same reason: the callee function is not used directly, as with imports, but reused to create a new function. + +Alternative options like `delegate` or `forward` could also be considered, but they would benefit less from users' familiarity with `use` items. + +↩ [_Syntax_](#syntax) + +#### Why is list delegation supported? + +The syntax cost of supporting it is negligible compared with the benefit. Specifically: + +1. Individual delegation is very close to a regular function call in terms of the amount of code written and is not particularly useful on its own. One of the main benefits of delegation comes from being able to delegate multiple items at once, avoiding repetitive declarations. +2. It is not a new concept in Rust, as `use` declarations already support lists. +3. Some form of it appears in many prior attempts at delegation, demonstrating users' interest in this capability: + 1. `use expression for name_1, name_i` in [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406) + 2. `delegate fn name_1, fn name_i to expression` in [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393) + 3. `export path . { sel_1, ..., sel_n }` in [Scala 3](https://docs.scala-lang.org/scala3/reference/other-new-features/export.html) + + +↩ [_Syntax_](#syntax) + +#### Why is glob delegation supported? + +The syntax cost of supporting it is negligible compared with the benefit. Specifically: + +1. Individual delegation is very close to a regular function call in terms of the amount of code written and is not particularly useful on its own. One of the main benefits of delegation comes from being able to delegate multiple items at once, avoiding repetitive declarations. +2. It is not a new concept in Rust, as `use` declarations already support globs. +3. Some form of it appears in many prior attempts at delegation, demonstrating users' interest in this capability: + 1. `use expression` in [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406) + 2. `delegate * to expression` in [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393) + 3. The `by` clause forwards an entire interface in one declaration in Kotlin. + 4. `#[delegate(Trait)]` delegates every method of `Trait` in [crates.io/ambassador](https://crates.io/crates/ambassador). + 5. `export name.*` in [Scala 3](https://docs.scala-lang.org/scala3/reference/other-new-features/export.html) + +↩ [_Syntax_](#syntax) + +#### Why is renaming supported? + +The syntax cost of supporting it is negligible compared with the benefit. Specifically: + +1. This will allow delegation from a trait implementation to a function that is not a method of the trait and has a different name from those defined in the trait. +
+ + Example: renaming in a trait implementation. + + ```rust + impl Default for BTreeSet { + reuse BTreeSet::::new as default; + } + ``` + +
+2. It is not a new concept in Rust, as `use` declarations already support renaming. +3. Some form of it appears in many prior attempts at delegation, demonstrating users' interest in this capability: + 1. `#[call(name)]` attribute in [crates.io/delegate](https://crates.io/crates/delegate) + 2. In [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406) and [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393), renaming is a possible extension. + 3. `export A as B` in [Scala 3](https://docs.scala-lang.org/scala3/reference/other-new-features/export.html) + +↩ [_Syntax_](#syntax) + +#### Why doesn't a delegation item provide syntax for introducing its own generics? + +Consider the example: + +```rust +pub fn to_vec(s: &[T], alloc: A) -> Vec { + T::to_vec(s, alloc) +} +``` + +In principle, we could support this delegation pattern with syntax such as `reuse T::to_vec;`. However, this would exceed our syntax budget (see [_guiding principles_](#design-guiding-principles)). + +↩ [_Syntax_](#syntax) + +#### Why doesn't a delegation item provide syntax for argument or return-value transformations? + +There are several transformations one might reasonably want from the delegation feature: +- Return value: converting the callee's return value using `.into()`, unwrapping a `Result`/`Option` with `.unwrap()`, awaiting a future returned by the callee with `.await`, etc. +- Input arguments: reordering arguments or calling a method like `.as_ref()`, etc. + +To support these transformations in their most general form, delegation items would need something closer to preprocessing and postprocessing closures. We do not support these in the RFC, in accordance with our [_guiding principles_](#design-guiding-principles). + +↩ [_Syntax_](#syntax) + +#### Why are glob delegations restricted? + +Glob delegations are only allowed in implementations, but not in modules or blocks. +Modules can contain imports, both glob and single, and both make it harder to determine the set of names that would need to be "filtered out" from the glob delegation during its expansion. +Also, the motivation for glob delegations in modules is not as strong as the motivation for glob delegations in impls, which can be used to delegate whole trait impls, so the additional complexity doesn't pull its weight. + +Glob delegations can only refer to traits, but not to modules. +Modules can contain imports, both glob and single, and both make it harder to determine the set of names that would be produced by the glob delegation during its expansion. +Modules typically contain other items rather than just functions, and delegating to them would result in errors with the current rules. +Similarly, the motivation for glob delegation from modules is not very strong, and the complexity also doesn't pull its weight. + +List delegations list all their names explicitly, so they don't need any similar restrictions. + +↩ [_Glob delegation_](#glob-delegation) + +#### Why can names from glob delegations be overridden by explicit items? + +TODO: explain the rationale. + +↩ [_Glob delegation_](#glob-delegation) + +#### Why are methods with default bodies included in glob delegation? + +TODO important: explain the rationale.
+TODO drawback: discuss semver hazards from using glob delegations to trait items default bodies.
+TODO: standard derives tend to not generate impls for methods with default bodies (Clone, Hash, Ord, PartialEq, PartialOrd - no defaults, Eq - some defaults). + +↩ [_Glob delegation_](#glob-delegation) + +#### Why are qualified paths used for call disambiguation? + +Rust distinguishes between two kinds of function invocation. The first kind consists of [method call expressions](https://doc.rust-lang.org/reference/expressions/method-call-expr.html), which have the form `receiver.method(args...)`. They are resolved to associated methods that take a receiver argument. Resolution in that case requires additional analysis by the compiler: the receiver may be automatically dereferenced, borrowed, or coerced. If more than one method is applicable, the compiler emits an error. The second kind consists of [fully qualified calls](https://doc.rust-lang.org/reference/expressions/call-expr.html#r-expr.call.desugar), which can be used to resolve such ambiguities. + + +From the perspective of delegation, the alternatives can be categorized as follows: + +1. Resolve the callee from the method name alone. + + [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406) and [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393) suggested using only the method name to resolve the callee. This covers the most common scenario: delegating a trait implementation to another implementation of the same trait. However, this syntax does not generalize naturally to other caller/callee combinations ([?](#why-can-delegation-items-be-declared-in-any-position)) since it can lead to ambiguities in a similar way to method calls. + +2. Resolve the callee from the fully qualified path. + + This approach covers every possible caller/callee combination ([?](#why-can-delegation-items-be-declared-in-any-position)) without ambiguity, but it requires more verbose and explicit syntax. + +3. Use keywords as disambiguators. + + One of the suggestions in [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393) is to use keywords (`trait`/`impl`/`fn`) to disambiguate the callee, for example, `reuse trait TraitName { expression }`. However, this approach doesn't generalize well to generic contexts. For example, it cannot distinguish between multiple generic implementations of the same trait. + + +The second option has been chosen for this proposal: + +1. The first reason is that fully qualified paths already provide a uniform and well‑understood mechanism for disambiguation. Reinventing a separate keyword‑based approach (or any other alternative) would add unnecessary complexity. +2. The second reason is that the first option has already been proposed twice, in [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406) and [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393). Rather than attempt the same approach a third time, this proposal comes at the problem from a different angle: name-based resolution can be reintroduced later as pure syntactic sugar layered on top of that mechanism. That keeps the door open to the first option in a forward-compatible way. + +
+ + Example: disambiguation of methods without a receiver. + +Consider a trait method without a receiver. In regular Rust code, calling such a method requires specifying the particular implementation of the trait, for example: + +```rust +trait Trait { fn foo(); } + +impl Trait for Inner { fn foo() {} } + +impl Trait for Outer { + fn foo() { Trait::foo(); } // ERROR +} + +impl Trait for Outer { + fn foo() { ::foo(); } // OK +} +``` + +The path `Trait::foo` alone is insufficient because multiple implementations may provide the same method. The same principle applies to delegation: + +```rust +impl Trait for Outer { reuse Trait::foo; } // ERROR +``` + +Allowing `Self` in delegation paths makes this possible: + +```rust +impl Trait for Outer { reuse ::foo; } // OK +``` + +
+ +
+ + Example: disambiguation of generic methods. + +Consider multiple implementations of the same trait with generic parameters. Rust code calling a method of such a trait requires specifying the particular generic arguments, for example: + +```rust +trait Trait { fn foo(&self) {} } + +impl Trait for Inner { fn foo(&self) {} } + +impl Trait<()> for Inner { fn foo(&self) {} } + +impl Trait for Outer { + fn foo() { Trait::foo(); } // ERROR +} + +impl Trait for Outer { + fn foo(&self) { Trait::<()>::foo(&self.0) } // OK +} +``` + +The path `Trait::foo` alone is insufficient because multiple implementations may provide the same method. The same principle applies to delegation: + +```rust +impl Trait for Outer { reuse Trait::foo { self.0 } } // ERROR +``` + +Allowing generic arguments in delegation paths makes this possible: + +```rust +impl Trait for Outer { reuse Trait::<()>::foo { self.0 } } // OK +``` + +
+ +Also see [_Future possibilities: Name-based resolution as sugar_](#name-based-resolution-as-sugar) + +↩ [_Paths and name resolution_](#paths-and-name-resolution) + +#### Why is the delegation resolution the corresponding trait method in trait implementations? + +With the _“Refined trait implementations”_ RFC ([rust-lang/rfcs#3245](https://github.com/rust-lang/rfcs/pull/3245)), an implementation signature may be more specific than the one declared in the trait. + +If a delegation item is in a trait implementation (e.g., `impl Trait for Type { /*delegate foo*/ }`), we have two options: + +1. Inherit information from the function resolved via the callee path. + + This option supports refined implementations. + +2. Inherit information from the trait method itself. + + This option supports cases where the callee has a different signature but can still be called because of argument or return-value coercions: + + ```rust + trait MirPass<'tcx> { + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>); + } + + trait MirLint<'tcx> { + fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>); + } + + pub(super) struct Lint(pub T); + + impl<'tcx, T> MirPass<'tcx> for Lint + where + T: MirLint<'tcx> + { + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + self.0.run_lint(tcx, body) + } + } + ``` + Here, `run_lint` accepts `&Body<'tcx>`, while the trait method `run_pass` requires `&mut Body<'tcx>`. The delegation can work because `&mut T` can coerce to `&T`. However, if the generated function copied its signature from the callee, `run_pass` would instead take `&Body<'tcx>`, violating the trait definition and resulting in a compilation error. + +The `#[refine]` attribute proposed by RFC 3245 could potentially be used to switch between these behaviors. We suggest inheriting signatures from the trait by default. + +↩ [_Desugaring of individual delegation_](#desugaring-of-individual-delegation-signature) + +#### Why are function qualifiers copied unchanged? + +The function header comprises qualifiers such as `const`, `async`, `unsafe`, and `extern "ABI"`. The following alternatives exist: + +1. Behave identically to regular functions. + + Delegation items use the same defaults: `const`, `async`, and `unsafe` are omitted, and `extern "Rust"` is used. Specifying a qualifier overrides the corresponding default for the generated function. This works when the callee also uses the default qualifiers. If the callee uses different qualifiers: + + - `const`: If the callee is `const` and the delegation item is not, there is no problem: a `const` function can be called from a non-`const` function. However, the delegation item cannot be called from a const context unless `const` is also specified on the delegation item. + - `ABI`: A mismatch here does not prevent the call from compiling, but it is difficult to see where that would be useful, and the user usually would have to restate the ABI for the delegation item. + - `unsafe`: Calling an `unsafe` function from a non-`unsafe` function requires wrapping the call in an `unsafe` block. We do not want this to happen silently, so the delegation item would have to be marked `unsafe`. Otherwise, the compiler would emit an error. + - `async`: Forwarding to an `async` callee from a non-`async` delegation item isn't possible without changing what gets generated. + +2. Inherit qualifiers from the delegation resolution. + +The proposal chooses to inherit all function qualifiers from the delegation resolution unchanged. The main problem with the first approach is verbosity. Matching the delegation resolution's qualifiers is essentially the only sensible choice, yet that approach would force users to repeat qualifiers for delegation items. + +↩ [_Desugaring of individual delegation_](#desugaring-of-individual-delegation-signature) + +#### Why is delegation of variadic functions not supported? + +There are pretty fundamental [implementability issues](https://github.com/rust-lang/rust/issues/127443) with passing through C variadic arguments. + +↩ [_Desugaring of individual delegation_](#desugaring-of-individual-delegation-signature) + +#### Why are inference placeholders allowed in paths? + +1. If substitution of own parameters is allowed, inference placeholders can be used to substitute only a subset of the parameters: + ```rust + pub fn foo(x: T, y: U) { /* impl */ } + reuse foo:: as bar; + ``` + This could desugar to: + ```rust + pub fn bar(x: i32, y: U) { + foo(x, y) + } + ``` + +2. If a parameter isn't present in the signature or where clauses, there is nothing to substitute and we can omit the full name. + +↩ [_Generics remapping_](#generics-remapping) + +#### Why are nested inference placeholders not allowed in paths? + +> [!WARNING] +> +> The idea below is unconventional, and this RFC does not propose it. It is included for completeness only: we are not currently aware of a use case for it, and treating a nested inference placeholder as an error remains the better default. + +Delegation could be made to work with nested inference placeholders (e.g., `Vec<_>`) by generating a new parameter for each placeholder: + + +```rust + fn foo(x: T) {} + reuse foo::>; +``` + +This could desugar into: +```rust +fn bar(x: HashMap) { + foo::>(x) +} +``` + +↩ [_Generics remapping_](#generics-remapping) + + +#### Why might own generic parameters need to be substituted? + +Consider the example: + +```rust +pub const fn max_leb128_len() -> usize { /* impl */ 42 } + +pub const fn largest_max_leb128_len() -> usize { + max_leb128_len::() +} +``` + +With the ability to provide generic arguments for own parameters, the `largest_max_leb128_len` implementation could be replaced with the delegation item `reuse max_leb128_len::`. + +↩ [_Generics remapping_](#generics-remapping) + +#### Why is the `Self` type not substituted? + +In the name resolution section, we provided an example of how the `Self` type argument can be used to disambiguate a callee without a receiver. However, that argument does not participate in generic substitution; that is, the parent `Self` parameter always refers to the `Self` type of the current context. Consider the example: + +```rust +trait Iterator { + type Item; + fn any(&mut self, f: F) -> bool + where + Self: Sized, + F: FnMut(Self::Item) -> bool; +} + +pub struct UnordItems>(I); + +impl> UnordItems { + pub fn any bool>(mut self, f: F) -> bool { + self.0.any(f) + } +} +``` + +Suppose we replace the implementation of `UnordItems::any` with the delegation item `reuse Iterator::any { self.0 }`. The generated method looks like this: + +```rust +impl> UnordItems { + pub fn any::Item) -> bool>(mut self: &mut ?Self, f: F) -> bool { + Iterator::any(&mut self.0) + } + ... +} +``` + +Here, `F` can be copied directly because it is one of `Iterator::any`'s own parameters. However, `Self` is defined by the `Iterator` trait itself. To make the example work, we would need to: +1. Substitute `?Self` in `::Item` with `I` (e.g., with `reuse ::any { self.0 }`). +2. Substitute `?Self` in `mut self: &mut ?Self` with `UnordItems`, i.e., leave the parameter as a regular receiver. + +Thus, `?Self` would need to be substituted with different types depending on its position in the signature or where clauses. + +↩ [_Generics remapping_](#generics-remapping) + +#### Why might parent parameters need to be substituted? + +Consider the example: + +```rust +impl BTreeMap { + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow + Ord, + Q: Ord, + { /* impl */ false } +} + +impl BTreeSet { + reuse BTreeMap::contains_key as contains { self.map } // ERROR +} +``` + +The generated method looks like this: + +```rust +impl BTreeSet { + pub fn contains(&self, value: &Q) -> bool + where + ?K: Borrow + Ord, + Q: Ord, + { + BTreeMap::contains_key(&self.map, value) + } +} +``` + +Here, `Q` can be copied directly because it is one of `BTreeMap::contains_key`'s own parameters. However, `K`, defined in `BTreeMap`, needs to be remapped to `T`, defined in `BTreeSet` (we use `?K` to denote a parameter that has been copied but not yet remapped). + +To make the example work, the parameter can be explicitly substituted through the path: + +```rust +reuse BTreeMap::::contains_key as contains { self.map } +``` + +↩ [_Generics remapping_](#generics-remapping) + +#### What happens if unsubstituted parent parameters remain after substitution? + +Consider the example: + +```rust +impl BTreeMap { + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow + Ord, + Q: Ord, + { /* impl */ false } +} + +impl BTreeSet { + reuse BTreeMap::contains_key as contains { self.map } // ERROR +} +``` + +The `K` parameter defined in `BTreeMap` has not been substituted with the `T` parameter defined in `BTreeSet`, so the generated method looks like this: + +```rust +impl BTreeSet { + pub fn contains(&self, value: &Q) -> bool + where + ?K: Borrow + Ord, + Q: Ord, + { + BTreeMap::contains_key(&self.map, value) + } +} +``` + +Here, `?K` denotes a parameter that has been copied but not remapped. There are several options we could consider: + +1. Report an error. +2. Infer the parameter from the surrounding context: + 1. From the target block: `typeof(self.map) == BTreeMap::` + + We would need to type-check the function body before generating the full signature, which is not possible with the current compiler architecture. Similar problems exist for delegating to [_type-relative paths_](#paths-and-name-resolution). + + 2. The compiler could use a heuristic to substitute parameters defined in the implementation header (e.g., positional 1:1 matching or substituting parameters with the same names). But this approach is fragile and fails whenever generic parameters are reordered, partially instantiated, or renamed. + +3. We could generate a new parameter and substitute `?K` with it. This would not pass type checking in the example above, but it might be useful in other cases ([?](#what-happens-if-unsubstituted-parent-parameters-remain-after-substitution-part-2)). + +In this proposal, we suggest using the “report an error” option because it is the most conservative approach and requires generic arguments to be specified explicitly. Once the compiler architecture is sufficiently advanced, we can implement more sophisticated inference. + +Also see [_Future possibilities: More sophisticated inference of generic parameters_](#more-sophisticated-inference-of-generic-parameters) + +↩ [_Generics remapping_](#generics-remapping) + +#### Why are effective self types identified beyond the receiver? + +TODO: explain the rationale. + +↩ [_Effective self type identification_](#effective-self-type-identification) + +#### Why are effective self types not detected through type aliases or type equality? + +TODO important: explain the rationale.
+TODO: future possibilities - allow users to opt in to marking types as effective self types, or use type equality checks and allow users to opt out.
+TODO: future possibilities - extend the set of uses of the effective self type to which the conversions apply. + +↩ [_Effective self type identification_](#effective-self-type-identification) + +#### Why are specific smart pointers used for detecting effective self parameters? + +TODO important: explain the rationale.
+TODO: future possibilities - extend the set of uses of the effective self type to which the conversions apply.
+TODO: think about [field projections](https://github.com/BennoLossin/rfcs/blob/field-projection-v2/text/3735-field-projections.md) + +↩ [_Effective self type identification_](#effective-self-type-identification) + +#### Why are self return types limited to bare self type? + +TODO important: explain the rationale.
+TODO: future possibilities - extend the set of uses of the effective self type to which the conversions apply. + +↩ [_Effective self type identification_](#effective-self-type-identification) + +#### Why is the target block a block expression? + +In contrast to [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406) and [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393), this RFC uses a block rather than a bare expression (e.g., a hypothetical `reuse prefix::name from expr;`) because a block expression can contain many statements. While having multiple statements during delegation is expected to be a niche use case, anchoring the syntax to the most general form is consistent with our [_guiding principles_](#design-guiding-principles). + +↩ [_Target block_](#target-block) + +#### Why is the target block unrestricted? + +In feedback on [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406), it was suggested that delegation be limited to fields. This suggestion was adopted in [rust-lang/rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393). However, we see no compelling reason for this restriction either from an implementation perspective or from the perspective of the language itself. Also see [_guiding principles_](#design-guiding-principles). + +↩ [_Target block_](#target-block) + +#### Why can target blocks be allowed without effective self parameters? + +TODO important: explain the rationale.
+TODO drawback: discuss future compatibility issues with identity in target blocks + +↩ [_Target block_](#target-block) + +#### Why is `self` the implicit target expression? + +TODO: explain the rationale. + +↩ [_Target block_](#target-block) + +#### Why are statements not passed to the call? + +Suppose we have a delegation item: + +```rust +reuse path::name { let x = something; self.get(x) } +``` + +There are two possible ways to generate the call: +- Pass the block expression unchanged: + ```rust + path::name(..., { let x = something; self.get(x) }, ...,) + ``` +- Hoist the statements out of the block: + ```rust + let x = something; + path::name(..., self.get(x), ...,) + ``` + +Passing the whole block suppresses all the possible adjustments and moves the trailing expression out of the block, which is not what we want. +The more detailed discussion of the choice can be found in [this github comment](https://github.com/rust-lang/rfcs/pull/3530#issuecomment-2197170600). + +↩ [_Body desugaring_](#body-desugaring) + +#### Why are method-call adjustments applied to effective self parameters? + +TODO important: explain the rationale. + +↩ [_Body desugaring_](#body-desugaring) + +#### Why are the delegation resolution's own generic parameters substituted as arguments to the final segment? + +Suppose we have a delegation item: + +```rust +fn foo(x: i32) {} +reuse foo as bar; +``` + +There are two possible ways to generate the call: +- Propagate generic parameters to the call: + ```rust + fn bar() { foo::() } // Ok + ``` +- Do not propagate generic parameters to the call: + ```rust + fn bar() { foo() } // ERROR: type annotations needed + ``` + +The first option should be chosen because otherwise the generated call may fail with a type inference error. + +↩ [_Body desugaring_](#body-desugaring) + +#### Why is return type wrapping restricted to single-field structs? + +TODO important: explain the rationale. + +↩ [_Return type wrapping_](#return-type-wrapping) + +#### What happens if unsubstituted parent parameters remain after substitution? Part 2. + +> [!WARNING] +> +> The idea below is unconventional, and this RFC does not propose it. It is included for completeness only: we are not currently aware of a use case for it, and treating an unsubstituted parent parameter as an error ([_Part 1_](#what-happens-if-unsubstituted-parent-parameters-remain-after-substitution)) remains the better default. + +If a parent parameter remains unsubstituted in the signature or where clauses after substitution, one possible alternative is to generate an additional generic parameter. Consider the example: + +```rust +trait Ord: Eq + PartialOrd { + fn min(self, other: Self) -> Self + where + Self: Sized; +} + +fn min(v1: T, v2: T) -> T { + Ord::min(v1, v2) +} +``` + +Traits include an implicit `Self` parameter that can be modeled as a generic parameter `This: Trait`. If we allow parameters to be copied from the delegation resolution’s parent trait, the `min` implementation could be replaced with `reuse Ord::min;`. + +In principle, this could extend beyond `Self` to any parent parameter, but doing so raises multiple questions: + +- Rust requires lifetime parameters to be declared before type and const parameters, which means copied generics may need to be reordered. +- Default parameters are not permitted in functions. Therefore, either the default type must be used, or a new non-default parameter must be generated. +- Bounds also need to be copied. + +This extension is implemented in nightly rustc, and the example below compiles. + +
+ + Example: copying parent parameters. + +```rust +trait Trait1 {} + +trait Trait<'a, A: Trait1, C = i32> { + fn foo<'b, B>(&self, x: A, y: B, c: C); +} + +reuse Trait::foo; +``` + +A conceptual desugaring might look like this: + +```rust +fn foo<'a, 'b, This, A: Trait1, B>(this: &This, x: A, y: B, c: i32) +where + This: Trait<'a, A>, +{ + >::foo::(this, x, y, c) +} +``` + +
+ +↩ [_Generics remapping_](#generics-remapping) + +### Alternatives to this RFC + +#### Macros + +See [_Prior art: delegate_](#cratesiodelegate) and [_Prior art: ambassador_](#cratesioambassador) for a closer look at the two most widely used delegation crates. + +Both show that delegation can already be built as a library, with no change to the language, and both are mature and reasonably ergonomic. However, both are ultimately limited by what a macro can see: macros do not have access to type information such as the callee's resolved signature or the methods of a trait. + +Closing this gap fully would require the macro to see type information during expansion, which is the [_reflection_](#reflection) capability discussed as an alternative below. + +#### Reflection + +An alternative approach to delegation in Rust would be some form of compile-time reflection. Given the ability to inspect type information such as function signatures during macro expansion, delegation could be implemented as a third-party library, removing the need for dedicated language support. + +However, reflection is a large and complex feature that may take years to implement and stabilize. Even if it becomes available, it is not clear that it would be a suitable mechanism for delegation. + +Work in this direction is already being explored. See the [reflection project goal](https://github.com/rust-lang/rust-project-goals/issues/406). + +#### Embedding + +Rust could instead adopt some form of type embedding, where an anonymous field's methods are automatically "promoted" onto the outer struct's method set. + +Go has a working version of this idea. See [_Prior art: Type embeddings in Go_](#type-embeddings-in-go). + +[rust-lang/rfcs#2431](https://github.com/rust-lang/rfcs/issues/2431), opened in 2018, sketches a mechanism for Rust. The issue was posted as a rough idea seeking feedback, but it received little response and remains open with no further activity. + +#### Language support for newtypes + +An alternative to this RFC would be to add language support specifically for newtypes, allowing requested traits to be derived automatically. This narrower idea has been proposed repeatedly over the years: [rust-lang/rfcs#261](https://github.com/rust-lang/rfcs/issues/261), [rust-lang/rfcs#186](https://github.com/rust-lang/rfcs/pull/186), [rust-lang/rfcs#949](https://github.com/rust-lang/rfcs/pull/949), [rust-lang/rfcs#2242](https://github.com/rust-lang/rfcs/pull/2242), [rust-lang/rfcs#3596](https://github.com/rust-lang/rfcs/issues/3596), [rust-lang/rfcs#3951](https://github.com/rust-lang/rfcs/pull/3951). + +The last attempt ([rust-lang/rfcs#3951](https://github.com/rust-lang/rfcs/pull/3951)) was closed by the lang team with a [message](https://github.com/rust-lang/rfcs/pull/3951#issuecomment-4917471822): + + +> We gave this a brief review in our @rust-lang/lang meeting today. +> +> The meeting consensus was that we don't really see the need to use a tuple struct as a problem to be solved; we agree that it'd be nice to have easier ways to delegate trait impls and so forth (like a delegation RFC), but adding a new concept (newtype) that is still effectively-a-struct-but-different doesn't feel like enough of a win to warrant expanding our language surface in this way. +> +> Thank you for opening the PR! It's always great to see suggestions and thoughts on how to make Rust better. + + +#### Inheritance + +Rust could instead adopt some form of inheritance closer to what object-oriented languages provide. However, inheritance has been discussed extensively in the context of Rust, and it is generally not considered aligned with the language's design philosophy. + +Also see the [Rust Book](https://doc.rust-lang.org/book/ch18-01-what-is-oo.html#inheritance-as-a-type-system-and-as-code-sharing). + +## Prior art +[prior-art]: #prior-art + +### Delegation or similar mechanisms in other languages + +#### [Export clauses in Scala](https://docs.scala-lang.org/scala3/reference/other-new-features/export.html) + +Scala 3's `export` clause has the form `export path . { sel_1, ..., sel_n }` and defines aliases for selected members of an object. + +
+ + Example: Scala export clause. + +```scala +class Inner: + def hello(): String = "hello" + +class Outer(inner: Inner): + export inner.hello + +@main def run(): Unit = + println(new Outer(new Inner()).hello()) +``` + +
+ +Its selectors line up closely with this proposal's three function delegation forms: a single selector corresponds to individual delegation, multiple selectors correspond to list delegation, and a wildcard selector (`*`) corresponds to glob delegation. + +`x as y` renames a member on export, using the same `as` keyword that this RFC uses for renaming. + +#### [Delegation in Kotlin](https://kotlinlang.org/docs/delegation.html) + +Kotlin supports interface delegation natively via a `by` clause on the supertype list: `class Derived(b: Base) : Base by b` implements `Base` for `Derived` by forwarding every one of its methods to `b`. It is close in spirit to this proposal's glob delegation. + +Kotlin also lets `Derived` override individual delegated members instead of taking all of them from `b`. + +Kotlin [extends](https://kotlinlang.org/docs/delegated-properties.html) the same `by` keyword to individual properties, e.g., `val x: Int by lazy { computeX() }`. There, the expression after `by` is a delegate object providing `getValue` and `setValue` operator functions that the compiler invokes whenever `x` is read or written. This is a related but distinct feature with no direct equivalent proposed here. + +#### [Type embeddings in Go](https://go.dev/ref/spec#Struct_types) + +Go has no inheritance either and addresses the same problem through struct embedding. A struct field declared with only a type, no name, is _embedded_. The embedded value's fields and methods become available directly on the outer struct (`outer.Method()` instead of `outer.inner.Method()`). + +
+ + Example: Go struct embedding. + +```go +package main + +import "fmt" + +type Inner struct{} + +func (Inner) Hello() { + fmt.Println("hello world") +} + +type Outer struct { + Inner + Name string +} + +func main() { + o := Outer{Inner: Inner{}, Name: "name"} + o.Hello() // promoted from Inner; no o.Inner.Hello() needed +} +``` + +
+ +### Related proposals in Rust + +#### [rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406) (2015, closed) + +Delegation was first proposed in [rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406). This RFC introduces new syntax within trait `impl` blocks, permitting a type to forward an entire trait implementation (or selected items) to a field or arbitrary expression that already implements that trait. The proposed syntax has the following forms: +- `impl Trait for Type { use expression; }` - delegates all methods of the trait.
+- `impl Trait for Type { use expression for name_1 (, name_i)*; }` - delegates a subset of the trait's methods (one or more, listed by name). + +where `typeof(expression)` implements `Trait`. + +The main reasons for rejecting the proposal were: + +1. _Unclear semantics._ It's not clear what kinds of expressions are allowed in the delegation body. The behavior of `self` is underspecified. The mechanism for desugaring is not defined. Also see this [comment](https://github.com/rust-lang/rfcs/pull/1406#issuecomment-269175112). +2. _Forward compatibility._ The RFC intentionally leaves many features for future work, but there was insufficient evidence that the proposed design could be clearly extended to those features without breaking semantics and requiring a redesign. + +#### [rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393) (2018, closed) + +Delegation was proposed again in [rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393). The design was stricter to address the semantic ambiguities of the earlier proposal. The syntax has the following forms: +- `impl Trait for Type { delegate * to expression; }` - delegates all methods of the trait.
+- `impl Trait for Type { delegate fn name_1 (, fn name_i)* to expression; }` - delegates a subset of the trait's methods (one or more, listed by name). + +where `expression` resolves to a field of `self` (e.g., `self.field`) and `typeof(expression)` implements `Trait`. + +Delegation is allowed only for methods that take a receiver by value, by shared reference, or by mutable reference. The proposed desugaring scheme translates a delegation item into a [method call](https://doc.rust-lang.org/reference/expressions/method-call-expr.html). + +The main reasons for rejecting the proposal were: + +1. The second proposal was [postponed](https://github.com/rust-lang/rfcs/pull/2393#issuecomment-816822011) because of the lang team's limited bandwidth. +2. Additionally, forward compatibility concerns were never fully addressed. + +#### [rfcs2375](https://github.com/rust-lang/rfcs/pull/2375) (2018, open) + +This RFC proposes an `#[inherent]` attribute that allows a trait implementation's methods to be called directly on a type without bringing the trait into scope. For example, given: + + +```rust +#[inherent] +impl Bar for Foo { ... } +``` + +The methods defined in `Bar` can be called directly on instances of `Foo`, even if `Bar` is not in scope. The RFC defines `#[inherent]` as sugar for a forwarding inherent method: + +```rust +impl Foo { + #[inline] + pub fn bar(&self) { ::bar(self); } +} +``` + + +Later in the PR discussion, [nikomatsakis proposed](https://github.com/rust-lang/rfcs/pull/2375#issuecomment-1722647937) replacing `#[inherent]` with `use`, which is almost the same as the delegation item `pub reuse Bar::bar;` under this RFC. + +#### [rfcs#3591](https://github.com/rust-lang/rfcs/pull/3591) (2024, merged) + +This RFC allows a `use` declaration to bring a trait's associated functions and constants into scope by path, e.g., `use SomeTrait::some_fn;`. This is not delegation: `use Trait::func` creates a local name for an existing associated function and does not define a new item. However, the same use case can be expressed through the delegation feature. + +#### [rfcs#3911](https://github.com/rust-lang/rfcs/pull/3911) (2026, open) + +This RFC allow deriving an implementation of the `Deref` trait using `#[derive(Deref)]` on structs and enums, which allows emulating delegation with `Deref` coercions more conveniently. + +### Crates + +#### [crates.io/delegate](https://crates.io/crates/delegate) + +`delegate` is the most widely used crate for delegation. It implements the `delegate!` declarative macro, which delegates method calls to selected expressions. + +
+ + Example: delegate macro. + +```rust +struct Inner; +impl Inner { + pub fn method_res(&self, num: u32) -> Option { Some(num) } +} + +struct Wrapper { + inner: Inner +} + +impl Wrapper { + delegate! { + to self.inner { + // calls method_res, unwraps the result, then calls into + #[unwrap] + #[into] + #[call(method_res)] + pub fn method_res_into(&self, num: u32) -> u64; + } + } +} +``` + +
+ +_Strengths_: + +1. It supports a broad range of transformations through attributes such as `#[into(u64)]`, `#[unwrap]`, `#[await(true/false)]`, and many others, which can modify the signature or body of the generated method. This makes the macro applicable to a wide range of delegation patterns. +2. It is not limited to trait implementations. + +_Weaknesses_: + +1. Declarative macros have no access to the callee's actual signature. Every delegated method's signature must be restated by hand in the macro definition. + + +#### [crates.io/ambassador](http://crates.io/crates/ambassador) + +`ambassador` is the second most popular crate for delegation. In contrast to [delegate](https://crates.io/crates/delegate), it uses procedural macros rather than declarative macros. + +
+ + Example: ambassador macro. + +```rust +use ambassador::delegatable_trait; + +#[delegatable_trait] +pub trait Trait { + fn method(&self, input: &str) -> String; +} + +pub struct Inner; + +impl Trait for Inner { + fn method(&self, input: &str) -> String { + String::new() + } +} + +#[derive(Delegate)] +#[delegate(Trait)] +pub struct Outer(Inner); +``` + +
+ +_Strengths_: + +1. Unlike `delegate`, the callee's signature does not need to be restated at each delegation site. + +_Weaknesses_: + +1. It supports a narrower range of delegation patterns than `delegate`: Ambassador can only delegate trait implementations, delegates only to fields, and does not support transformations of the delegated method's signature. +2. The trait being delegated must be annotated with `#[delegatable_trait]`. For foreign traits, it must instead be redeclared locally with `#[delegatable_trait_remote]`. +3. In `#[delegate(..., target = "self")]` or `#[delegate(..., where = "A: Shout")]`, expressions are specified as strings rather than in regular Rust syntax. + +## Unresolved questions +[unresolved-questions]: #unresolved-questions + +The questions below are not expected to block acceptance of this RFC. They can be settled during implementation or before stabilization. + +### Which attributes should be added by default? + +Certain attributes may be reasonable to add or copy from the callee by default. The current implementation adds the `#[inline]` attribute and copies the `#[must_use]` attribute. + +There should also be a way to opt out of default attributes when they are not desired. For `#[inline]`, this may be done with `#[inline(never)]` on the delegation item, but the appropriate mechanism depends on the attribute, and some attributes may have no corresponding way to opt out. + +↩ [_Reference-level explanation_](#reference-level-explanation) + +### Should the visibility of the delegation item be restricted? + +The ability to specify a reused function's visibility independently of the original's provides welcome flexibility, yet it introduces a potential semver hazard: + +```rust +fn foo(x: T) { /* impl */ } +pub reuse foo as bar; +``` + +If the signature of `foo` changes, the generated function `bar` changes accordingly. In regular Rust code, such a signature change would cause a type error at every call site, forcing the author to update callers. With `reuse`, however, the change propagates silently. As a result, modifications intended to be internal may accidentally become breaking changes for downstream crates. + +Taking this into consideration, several design choices are possible: + +1. The visibility of the generated function is taken solely from the callee. +2. The visibility of the generated function cannot exceed the visibility of the reused function. In other words, delegation may only preserve or reduce visibility, never increase it. +3. The user explicitly controls visibility. + +We prefer to give the user full control while also adding a lint that warns if a generated function reachable from other crates forwards to a function that is not reachable. + +↩ [_Reference-level explanation_](#reference-level-explanation) + +## Future possibilities +[future-possibilities]: #future-possibilities + +Several extensions could be added on top of the core feature without changing its fundamental semantics. At the same time, the scope for such extensions is relatively limited. + +### Name-based resolution as sugar + +A shorter syntax that infers the callee from a bare method name could be layered on top of fully qualified paths. + +↩ [_Paths and name resolution_](#paths-and-name-resolution) + +### More sophisticated inference of generic parameters + +We could implement a more advanced mechanism for inferring unsubstituted generic parameters, allowing users to specify fewer generic arguments explicitly. + +See ([?](#what-happens-if-unsubstituted-parent-parameters-remain-after-substitution)) and ([?](#what-happens-if-unsubstituted-parent-parameters-remain-after-substitution-part-2)) for more details. + +↩ [_Generics remapping_](#generics-remapping) + +### Support for delegating types and consts + +We could support desugaring for types and consts as follows. +It would allow using glob delegation and `reuse impl` more effectively, without manual overrides for types and constants. + + +```rust +impl Trait for S { + reuse Trait::{Item, MAX, func} { self.0 } +} + +impl Trait for S { + type Item = ::Item; + const MAX = ::MAX; + fn func(&self) -> u32 { + ::func(&self.0) + } +} +``` + +For associated constants the delegation rules should naturally follow from the function delegation rules, since constants are more or less equivalent to functions with zero parameters and return type matching the constant's type. + +However, there are some complications. +For example, types live in the type namespace, while functions and constants live in the value namespace. A single qualified path doesn't say which namespace to pull from, so `Trait::name` is ambiguous whenever `Trait` has both an associated type and an associated function or constant called `name`. +This can be solved by introducing a disambiguator for types. One of the suggestions in [rfcs#2393](https://github.com/rust-lang/rfcs/pull/2393) is to use `fn`/`type`/`const` keywords. + +For these reasons, we would like to postpone delegation of types and constants. + +↩ [_Reference-level explanation_](#paths-and-name-resolution) + +### Empty list delegation + +Supporting empty list or glob delegations, such as `reuse prefix::{};` or `reuse MarkerTrait::*;`, requires keeping a stub for the prefix in the AST and HIR after the list/glob expansion, so the prefix can be resolved and checked for stability. + +The implementation therefore has some cost for little benefit. Implementing this makes little sense unless the feature is accepted and stabilized. + +Not resolving the prefix and accepting `reuse nonexistent::path::{};` would be surprising. +Resolving the prefix but not checking it for stability would be a compatibility hazard (if an unstable API is removed). + +↩ [_List delegation_](#list-delegation) + +### Supporting type-relative paths + +Several approaches could be considered for supporting type-relative paths: +1. We could generate an incomplete body (e.g., without arguments), then use analysis passes in HIR to infer the missing information and complete body generation during lowering to MIR/THIR. +2. We could lower everything except delegation items, run the analysis passes, and then finish lowering the delegation items. + +This would require substantial compiler refactoring, so we do not have a strong opinion on this. -, so the general scheme may still be useful for this case. - -### Going from `target_expr_template` to generated code - -As mentioned above, when inferring the callee, all `self`s in `target_expr_template` are -effectively replaced with `value_of::()`. - -Similarly, when generating the final body, all `self`s in `target_expr_template` are replaced with -`arg0` of the generated function (which is often also `self`). - -If `arg0` is not transformed, then we could actually skip the target expression part in the -delegation syntax entirely. -```rust -// Explicit path -// For static methods and free functions this looks better than the full form with `{ self }`. -delegation_keyword prefix::name; - -// Inferred callee -// Doesn't make sense semantically because `self.name()` will just infinitely recurse. -delegation_keyword name; -``` - -# Implementation steps - -- 1. Support delegation flavor with explicit paths for the callee, and only for paths that can be -resolved early in `rustc_resolve` (that means `module::name` or `Trait::name`). -This will help us to focus on issues with signature inheritance, and get a working feature while -completely putting aside the questions about callee inference. -The callee signature will also be available very early, before even AST -> HIR lowering. -- 2. Same as above, but with support for type-relative explicit paths `Type::name`. -Now we'll need to be able to generate inherited signatures after some amount of type checking. -- 3. Support delegation flavor with inferred callee. -Need larger amount of type checking before being able to generate inherited signatures. -- 4. Support `RetMatch.SameUpToSelfType` cases. -- 5. Support `ArgsMatch.SameUpToSelfType` cases. -- 6. Consider supporting secondary layers of sugar like "glob delegation". - -Support for a list of multiple delegated functions in a single delegation item can be done in -parallel with any step after `1`. - -# Literature - -- https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming) -- https://kotlinlang.org/docs/delegation.html -- https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/newtype_deriving.html -- https://github.com/rust-lang/rust/issues/7773 (delegation of entire trait implementations) -- https://github.com/rust-lang/rust/issues/8353 (delegation of entire trait implementations, automatically derive traits on newtypes) -- https://github.com/rust-lang/rust/issues/9912 (implementation inheritance via traits) -- https://github.com/rust-lang/rust/issues/19597 (syntax extension to derive traits for a newtype) -- https://github.com/rust-lang/rfcs/pull/186 (newtype keyword) -- https://github.com/rust-lang/rfcs/issues/261 (newtype deriving) -- https://github.com/rust-lang/rfcs/issues/292 (delegation of entire trait implementations) -- https://github.com/rust-lang/rfcs/issues/299 (implementation inheritance via traits) -- https://github.com/rust-lang/rfcs/issues/349 (efficient code reuse) -- https://github.com/rust-lang/rfcs/issues/479 (delegation of entire trait implementations, automatically derive traits on newtypes) -- https://github.com/rust-lang/rfcs/pull/508 (newtype deriving) -- https://github.com/rust-lang/rfcs/pull/949 (castable newtypes) -- https://github.com/rust-lang/rfcs/pull/1406 (delegation of implementation) - main RFC -- https://github.com/rust-lang/rfcs/pull/1546 (fields in traits) -- https://github.com/rust-lang/rfcs/pull/2242 (semantic newtypes) -- https://github.com/rust-lang/rfcs/pull/2375 (inherent trait implementation) -- https://github.com/rust-lang/rfcs/pull/2393 (delegation) - rework of main RFC -- https://github.com/rust-lang/rfcs/pull/2429 (reserve delegate keyword) -- https://github.com/rust-lang/rfcs/pull/2874 (trait enums) -- https://github.com/rust-lang/rfcs/issues/3108 (delegation dup) -- https://github.com/rust-lang/rfcs/issues/3133 (delegation another dup) -- https://internals.rust-lang.org/t/syntactic-sugar-for-delegation-of-implementation/2633 (preface to main rfc) -- https://internals.rust-lang.org/t/3-weeks-to-delegation-please-help/5742 (preface to main rfc rework) -- https://internals.rust-lang.org/t/new-rfc-for-delegation-anyone-interested-in-contributing/6644 (preface to main rfc rework 2) -- https://internals.rust-lang.org/t/potential-rfc-delegation/13831 (dup) -- https://internals.rust-lang.org/t/pre-rfc-forwarding/13836 (dup) -- https://internals.rust-lang.org/t/adding-true-oo-capabilities-to-rust/16691 (tangentially related) -- https://internals.rust-lang.org/t/is-it-still-possible-to-make-progress-on-postponed-features-the-case-of-delegation/16868 (status update on 2 main RFCs N years later) -- https://internals.rust-lang.org/t/strong-type-aliases-type-copy/17754 (dup) -- https://crates.io/crates/delegate (fn delegation, lots of features and syntax, post- and pre-processing, alive) -- https://crates.io/crates/delegate-attr (fn delegation, a bit simpler than delegate, dead) -- https://crates.io/crates/ambassador (traits only, trait prototype registering, not very alive) -- https://crates.io/crates/enum_delegate (trait <-> enum polymorphism specifically, trait prototype registering, alive) +↩ [_Paths and name resolution_](#paths-and-name-resolution) From d476d8b4e8b81358f41b800bec7f485d1e1cdbbf Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 15 Oct 2026 16:25:19 +0300 Subject: [PATCH 05/16] Update code examples that compile on 2026-10-14 nightly --- text/0000-fn-delegation.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 942dcf568fe..e8eef924a24 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -295,7 +295,6 @@ impl Clone for BTreeSet { Both methods can be delegated using the list syntax shown above: - ```rust impl Clone for BTreeSet { reuse Clone::{clone, clone_from} { self.map } @@ -308,7 +307,6 @@ Both methods share the target block `{ self.map }`. The value returned by `Clone `Clone` has only these two methods, so instead of listing them we can delegate all of them with a glob: - ```rust impl Clone for BTreeSet { reuse Clone::* { self.map } @@ -317,7 +315,6 @@ impl Clone for BTreeSet { A glob delegation item behaves as if all the methods of the trait were listed, including those with default implementations. `clone_from` is such a method. A glob delegation can also be written as follows: - ```rust reuse impl Clone for BTreeSet { self.map } ``` From b869540090bc59549946feb4dd84ce9c1d763929 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 15 Oct 2026 16:55:04 +0300 Subject: [PATCH 06/16] Address feedback: link for eff self params, free fn delegation example --- text/0000-fn-delegation.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index e8eef924a24..e27f5aa6fed 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -723,7 +723,7 @@ See the [_body desugaring chapter_](#desugaring-of-individual-delegation-body) f #### Target block -The target block is an optional [block expression](https://doc.rust-lang.org/beta/reference/expressions/block-expr.html) ([?](#why-is-the-target-block-a-block-expression)) that is used to transform the delegation item's effective self parameters before they are forwarded to the resolved callee. There are no restrictions on the expressions that can be used inside the target block ([?](#why-is-the-target-block-unrestricted)). +The target block is an optional [block expression](https://doc.rust-lang.org/beta/reference/expressions/block-expr.html) ([?](#why-is-the-target-block-a-block-expression)) that is used to transform the delegation item's [effective self parameters](#effective-self-type-identification) before they are forwarded to the resolved callee. There are no restrictions on the expressions that can be used inside the target block ([?](#why-is-the-target-block-unrestricted)). Inside that block, `self` refers to an effective self parameter that will be transformed. If the generated function has no effective self parameters, then it's an error to specify the target block, unless the current delegation item was defined as part of a list or glob delegation in which at least one other item has an effective self parameter ([?](#why-can-target-blocks-be-allowed-without-effective-self-parameters)). @@ -992,6 +992,20 @@ pub fn to_string(f: impl FnOnce(&mut State<'_>)) -> String { +
+ Example: delegating from a free function to a free function. + +[example link](https://github.com/rust-lang/rust/blob/752b9bf8798c2ffc1d3fe2b804c04454366fc6d6/library/stdarch/crates/core_arch/src/x86_64/adx.rs#L26-L37) + +```rust +#[target_feature(enable = "adx")] +pub fn _addcarryx_u64(c_in: u8, a: u64, b: u64, out: &mut u64) -> u8 { + _addcarry_u64(c_in, a, b, out) +} +``` + +
+ etc. All these combinations appear in real-world code through regular calls, and each represents a potential use case for the delegation feature. Choosing which combinations to support is a design decision driven by multiple factors: the function call resolution algorithm, the available syntax budget, the frequency of the use case, and the extensibility to other cases. From 159342f2f3440d4ccbc902d8f6bf7c8163feebf2 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 15 Oct 2026 18:27:07 +0300 Subject: [PATCH 07/16] Rationale: Why are self return types limited to bare self type? --- text/0000-fn-delegation.md | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index e27f5aa6fed..990db2cb530 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -1527,8 +1527,34 @@ TODO: think about [field projections](https://github.com/BennoLossin/rfcs/blob/f #### Why are self return types limited to bare self type? -TODO important: explain the rationale.
-TODO: future possibilities - extend the set of uses of the effective self type to which the conversions apply. +With this limitation we can generate conversion for the return value as a simple struct literal expression. +```rust +Wrapper { inner: callee(...) } +``` + +In a perfect world, the conversion would instead be done using something like a private trait impl. +```rust +>::wrap(callee(...)) +``` +The visibility of that impl would be limited to visibility of the `inner` field, this way we wouldn't give the whole world ability to convert from the inner type to the wrapper type, because that may be undesirable. + +For newtypes such trait impl could be implemented either fully automatically by the compiler, or semi-automatically using `derive(DelegationWrap)`. +In more complex cases the trait impl could be written manually by the user. +There would also be a blanket impl of `DelegationWrap` for every type allowing to convert it to itself, so the wrapping call could be emitted unconditionally. + +The problem is that we don't currently have private trait impls in the language. +So we are doing something that is forward compatible with them, in the scenario where such impls for newtypes are auto-provided by the compiler. + +The second potential extension is supporting return types like `Box` or `Rc`, that can be trivially created from `Self`. +The body desugaring then would look like +```rust +From::from(Wrapper { inner: callee(...) }) // or the same with `DelegationWrap::wrap` instead of `Wrapper { ... }` +``` + +This extension is currently implemented on nightly rustc. + +Whether `From` specifically or some other more specialized trait should be used for the conversion is a matter for discussion, many iterations of discussions about the `Try` trait conversions may serve as an inspiration. +One requirement for the trait is to have a blanket impl for converting any type to itself. ↩ [_Effective self type identification_](#effective-self-type-identification) From 03661f2a50f7873de33aff711714dfab773c699e Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 15 Oct 2026 19:05:08 +0300 Subject: [PATCH 08/16] Rationale: Why is return type wrapping restricted to single-field structs? --- text/0000-fn-delegation.md | 76 +++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 990db2cb530..f019396f320 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -1638,7 +1638,81 @@ The first option should be chosen because otherwise the generated call may fail #### Why is return type wrapping restricted to single-field structs? -TODO important: explain the rationale. +We currently generate conversion for the return value as a struct literal expression with a single field. +```rust +Wrapper { inner: callee(...) } +``` + +We could easily extend this expression with support for [default field values](https://github.com/rust-lang/rust/issues/132162). +```rust +Wrapper { inner: callee(...), .. } +``` +In that case "newtypes" like +```rust +struct Wrapper { + inner: Inner, + phantom: PhantomData = PhantomData, +} +``` +would also be supported. + +This however would create semver hazards for structures with public fields. + +If we selected `inner` in `Wrapper { inner: callee(...), .. }` to be the *first* field in the structure, then it would become a breaking change to reorder public fields in structures. + +
+ + Breaking change: reorder fields + +```rust +// Ok +pub struct Wrapper { + pub inner: Inner, + pub other: Other = default_other, +} + +// Not ok +pub struct Wrapper { + pub other: Other = default_other, + pub inner: Inner, +} +``` + +
+ +If we selected `inner` in `Wrapper { inner: callee(...), .. }` to be the *only non-default* field in the structure, then it would become a breaking change to add new default fields. + +
+ + Breaking change: adding a default field + +```rust +// Ok +pub struct Wrapper { + pub inner: Inner, + pub other: Other = default_other, +} + +// Not ok +pub struct Wrapper { + pub inner: Inner = default_inner, + pub other: Other = default_other, +} +``` + +
+ +The potential solutions could be: +- Generate the `..`, but then emit an error if it results in a semver hazard. +- Add some definition-site attribute for selecting the correct inner field: + ```rust + // Ok + pub struct Wrapper { + #[delegation_inner] + pub inner: Inner, + pub other: Other = default_other, + } + ``` ↩ [_Return type wrapping_](#return-type-wrapping) From db8a6445f1843f1fffcba2a782cfef9d788319e0 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 16 Oct 2026 17:28:45 +0300 Subject: [PATCH 09/16] Rationale: Why are effective self types not detected through type aliases or type equality? --- text/0000-fn-delegation.md | 58 ++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index f019396f320..595594c3cda 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -702,14 +702,14 @@ we also need to treat `Rhs` as an effective self type if it was obtained from th If the delegation resolution is an inherent method with `self`, then its corresponding type is considered an effective self type in the generated function. -In all other cases, types in signatures are not considered effective self types. In particular, effective self types are *not* detected by tracking uses of the `Self` type alias in impls (as opposed to the `Self` parameter in traits) or by checking type equality ([?](#why-are-effective-self-types-not-detected-through-type-aliases-or-type-equality)). +In all other cases, types in signatures are not considered effective self types. In particular, effective self types are *not* detected by tracking uses of the `Self` type *alias* in impls (as opposed to the `Self` parameter in traits) or by checking type equality ([?](#why-are-effective-self-types-not-detected-through-type-aliases-or-type-equality)). ```rust impl Struct { fn method(self, other1: Self, other2: Struct) {} } -// Types of `other1` and `other2` are not considered effective self types here. +// Types of `other1` and `other2` are not currently considered effective self types here. reuse Struct::method; ``` @@ -1511,9 +1511,57 @@ TODO: explain the rationale. #### Why are effective self types not detected through type aliases or type equality? -TODO important: explain the rationale.
-TODO: future possibilities - allow users to opt in to marking types as effective self types, or use type equality checks and allow users to opt out.
-TODO: future possibilities - extend the set of uses of the effective self type to which the conversions apply. +There's no good "minimal" or "conservative" way to detect effective self types, because we can get both false positive and false negative mis-detections. +Both applying the conversion (target block) when it should not be applied, and not applying the conversion when it should be applied is a mistake. + +The only conservative solution is refusing to choose and producing an error (maybe a feature gate error) for types that have type equality with the `Self` type (after nomalizing away all the associated types, etc), but are not considered effective self types by the current rules. +This solution would also be unfortunate, because it would reject many functions that could actually be delegated. + +The current rules only detect effective self types in the cases in which we are absolutely sure (`Self` parameter in traits, basically). + +We could try detecting more of such types using the next two methods: + +##### Consider uses of `Self` *type alias* in inherent impls to be an effective self type + +```rust +impl Struct { + fn method(remapped: &Self, not_remapped: &Struct) { } +} +``` + +Pros: +- Near zero chance of false positives, if something is marked as `Self` for an inner type method, then it's very clear that it will also use `Self` in the delegated wrapper type method. +- Easy to detect even at early stages of compilation. +- We can express both types that need to be remapped, and types that don't need to be remapped during delegation. + + +Cons: +- Makes type aliases more significant, previously it wasn't important whether some type was written directly or through a type alias. +- Introduces a new semver hazard, now it's a breaking change to change `Self` to `Struct` or back in `impl Struct { ... }` methods. + +A variation of this approach would be marking self types in signatures in some other way, e.g. with some `#[delegation_self]` attribute, but using the `Self` alias would probably be better than *that*. + +We think using `Self` aliases is a good way forward, and the pros overweigh the cons. +We can teach people to use the `Self` alias when they indeed mean the self type, and tools can detect the new semver-breaking changes. + +##### Consider types *equal* to `Self` type to be effective self types + +```rust +impl Struct { + fn method(remapped: &Self, also_remapped: &Struct) { } +} +``` + +Pros: +- No false negatives, if the type is not equal to `Self` then it definitely shouldn't be remapped during delegation. + +Cons: +- False positives are possible, same type may be used in signatures of both inner and wrapper type methods, it just *happens* to be the same type as `Self` for the inner type. This may happen especially often in the inner type is something common, e.g. `u32` or other arithmetic type. +- Need to define what type equality means, e.g. how lifetimes and associated types are treated. Some forms of type equality are hard to check early in compilation, and we need to decide whether something is an effective self type or not early during type AST lowering. +- An opt-out using some attribute like `#[not_delegation_self]` is possible, but it is worse than the opt-in using the `Self` alias from the alternative above. + +We don't think this is a good way forward. +But we could additionally feature gate delegations to methods with "something equal to `Self` but not using `Self` alias" in signatures, to decide later. ↩ [_Effective self type identification_](#effective-self-type-identification) From a6a3bb37fb8815e9d7b6d0a3a074faba500f2a73 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 16 Oct 2026 18:55:59 +0300 Subject: [PATCH 10/16] Rationale: Why are effective self parameters limited to references? --- text/0000-fn-delegation.md | 101 ++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 7 deletions(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 595594c3cda..2c81b79f4b0 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -687,7 +687,7 @@ impl Trait for Struct { fn method(self, other: Struct) -> Struct { unimplemented!() } } ``` -Let's call such types in signatures "effective self types". +Let's call such types in signatures "effective self types" (`EffSelf`). The following procedure is used for detecting effective self types. @@ -713,8 +713,15 @@ impl Struct { reuse Struct::method; ``` -If a function parameter's type is an effective self type, possibly wrapped in one of the references or smart pointers mentioned in [items.associated.fn.method.self-ty](https://doc.rust-lang.org/reference/items/associated-items.html#r-items.associated.fn.method.self-ty), then let's call it an "effective self parameter" ([?](#why-are-specific-smart-pointers-used-for-detecting-effective-self-parameters)). -If the function's return type is an effective self type, without any additional wrapping, let's call it a "self return type" ([?](#why-are-self-return-types-limited-to-bare-self-type)). +- If a function parameter's type has an effective self type inside it, then let's call it an *effective self parameter*. + If an effective self parameter has a type other than `EffSelf`, `&EffSelf` or `&mut EffSelf`, then an error is reported ([?](#why-are-effective-self-parameters-limited-to-references)). +- If the function's return type has an effective self type inside it, let's call it a *self return type*. + If a self return type has a type other than bare `EffSelf`, then an error is reported ([?](#why-are-self-return-types-limited-to-bare-self-type)). + +> [!NOTE] +> +> On nightly rustc effective self parameters and self return types are not limited to bare and referenced effective self types, in most cases conversions are attempted instead. These conversions have open questions about their design, see the [_rationale sections_](#why-are-effective-self-parameters-limited-to-references) linked above for their discussion.
+> On nightly rustc the first function parameter is also always considered an effective self parameter in absence of other effective self parameters, and is therefore converted using the target block. So explicit target blocks can be used for conversions even in free-to-free delegations. In the generated function body, effective self parameters are converted using the delegation's target block, and self return types are converted using newtype wrapping. See the [_body desugaring chapter_](#desugaring-of-individual-delegation-body) for details. @@ -1565,11 +1572,91 @@ But we could additionally feature gate delegations to methods with "something eq ↩ [_Effective self type identification_](#effective-self-type-identification) -#### Why are specific smart pointers used for detecting effective self parameters? +#### Why are effective self parameters limited to references? -TODO important: explain the rationale.
-TODO: future possibilities - extend the set of uses of the effective self type to which the conversions apply.
-TODO: think about [field projections](https://github.com/BennoLossin/rfcs/blob/field-projection-v2/text/3735-field-projections.md) +##### Two interpretations of target blocks + +There are two fundamental alternative interpretations for the target block (or target expression) - it can either be used for converting the "wrapped" version of `Self` like `Box`/`Pin<&mut Self>`/etc as it occurs in the signature (A), or for converting the `Self` type *itself* (B). +The difference between the two interpretations can be seen from delegating to a method like +```rust +impl Inner { + fn boxed_self(self: Box) {} +} +struct Wrapper { inner: Inner } + +// Interpretation A ("as it occurs") +impl Wrapper { + // Target expression contains the wrapping logic + reuse Inner::boxed_self { Box::new(self.field) } + // Desugars into + fn boxed_self(self: Box) { + Inner::boxed_self(Box::new(self.field)) + } +} + +// Interpretation B ("`Self` itself") +impl Wrapper { + // The wrapping logic is added by the delegation infra + reuse Inner::boxed_self { self.field } + // Desugars into: + // 1) Simple version, `self.field` can only be called once + fn boxed_self(self: Box) { + Inner::boxed_self(From::from(self.field)) + } + // 2) More advanced version, `self.field` can be called multiple times to support types like `Vec`. + // `DelegationMap` is a trait that can map from `SmartPtr` to `SmartPtr` using a `Fn(T) -> U` closure. + fn boxed_self(self: Box) { + Inner::boxed_self(::delegation_map(|this| this.field)) + } +} +``` + +The first interpretation is simpler, but the second one is definitely more powerful. +With the second interpretation we can delegate functions using both `Self` and `SmartPtr` as a part of a single list or glob delegation, using the same target block. +```rust +reuse Inner::{boxed_self, ref_self, ref_mut_self} { self.field }; + +// With interpretation A we'll have to split this into two items +reuse Inner::{ref_self, ref_mut_self} { self.field }; +reuse Inner::boxed_self { Box::new(self.field) }; +``` +We can also support functions using both `Self` and `SmartPtr` in the *same* signature. +```rust +// With interpretation A this is not delegatable at all. +fn foo(self: Self, other_self: Box); +``` + +If the design goes into the direction of interpretation B, then we'll also need to think how to support `Pin` smart pointers specifically, and how [field projections](https://github.com/BennoLossin/rfcs/blob/field-projection-v2/text/3735-field-projections.md) fit into this picture and the design of `DelegationMap`. + +The RFC and the nightly implementation currently use interpretation A ("as it occurs"), that's why the reference level explanation limits effective self parameters to `Self`, `&Self` and `&mut Self`, for future-compatibility with interpretation B. + +##### Some more on the interpretation A ("as it occurs") + +In this interpretation if we have an effective self parameter candidate `param: Something`, it is almost always better to try applying the target block conversion to it. +In the best case it will convert correctly and make the code compile, in the worst case it will produce a different error. + +Note that if the signature for an *individual* delegation has a single parameter with `Self` in it, we can always write a target block that will convert it successfully, e.g. change it from `self.field` to `Box::new(self.field)` for `Box`, like shown above. +So we expect delegations like this to be common: +```rust +// Delegate with a glob and field for most methods. +reuse Trait::* { self.inner } +// Override with an individual delegation for a few methods with "weirdly wrapped" `Self` +reuse Trait::boxed_self { Box::new(self.inner) } +``` + +As mentioned above, methods like `fn foo(self: Self, other_self: Box)`, lacking a single suitable block for all argument conversions, are basically undelegatable with this approach. + +One exception from the "always apply the conversion" rule is associated types, i.e. the case where `Something` is `Self::AssocTy`. + +Unfortunately, associated types are generally split into two classes. +- The first class consists of associated types behaving like `Add::Output`, they must be converted using the target block, because typically `Wrapper::Output == Wrapper`, and `Inner::Ouput == Inner`. +- The second class consists of associated types behaving like `Iterator::Item`, they must not be converted using the target block, because typically `WrapperIterator::Item == ElemType`, and `InnerIterator::Item == ElemType`, where `ElemType` is the same in both cases. + +So if we support `Add` we break `Iterator`, and vice versa. + +Implementation on nightly rustc currently applies target block conversions to all effective self parameters, except those having type `Self::AssocTy`. +So it supports `Iterator` and breaks `Add`. +Istead we should probably flip the default and convert all parameters with `Self` in their type by default, but add some attribute to `Iterator::Item` that will opt out of the conversion and keep `Iterator` working. ↩ [_Effective self type identification_](#effective-self-type-identification) From b6c8aa5ed96182306d6e639284ce70bad291c808 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 22 Oct 2026 17:30:04 +0300 Subject: [PATCH 11/16] Rationale: Why are methods with default bodies included in glob delegation? --- text/0000-fn-delegation.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 2c81b79f4b0..1842059bef6 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -881,7 +881,12 @@ Many cases of delegation require more than simple forwarding (e.g., transforming The delegation feature could potentially be implemented as a third-party library with compile‑time [_reflection_](#reflection) (if and when that becomes available). -TODO: collect remaining drawbacks from the rationale sections. +Language features reducing the amount of boilerplate code are less important now when majority of code is read and written by LLMs. + +The feature may potentially introduce a number of semver hazards: +- Delegating from a public function to a private function could lead to unintended breaking changes in public interfaces, but this can be caught by a lint ([_more_](#should-the-visibility-of-the-delegation-item-be-restricted)). +- Adding a non-delegatable method with default body to a trait may become a breaking change, [_depending on the design_](#why-are-methods-with-default-bodies-included-in-glob-delegation). +- Changing between `Self` type alias and `Struct` in function signatures inside `impl Struct { ... }` may become a breaking change, [_depending on the design_](#why-are-effective-self-types-not-detected-through-type-aliases-or-type-equality). ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -1149,9 +1154,21 @@ TODO: explain the rationale. #### Why are methods with default bodies included in glob delegation? -TODO important: explain the rationale.
-TODO drawback: discuss semver hazards from using glob delegations to trait items default bodies.
-TODO: standard derives tend to not generate impls for methods with default bodies (Clone, Hash, Ord, PartialEq, PartialOrd - no defaults, Eq - some defaults). +Glob delegation may either generate implementations for trait methods having default bodies or not generate them. +This RFC and implementation in nightly rustc currently use the former alternative. + +Pros of including methods with default bodies: +- If the delegation item type checks, then everything will work correctly. Assuming the typical `struct Wrapper(Inner);` setup, if the `Inner` type explicitly implements some of the default methods (for optimization, or to change behavior), those implementations will correctly propagate to `Wrapper`. + +Cons of including methods with default bodies: +- If a trait adds a new method that has default body, but the method is not delegatable for some reason (e.g. it has something like [`&[Self]`](https://doc.rust-lang.org/stable/std/hash/trait.Hash.html#method.hash_slice) in its signature), it will break code that glob-delegates from that trait. + This is somewhat similar to `dyn`-compatible traits accidentally adding new non-`dyn`-compatible methods with default bodies. +- The behavior of delegation becomes different from other features already familiar to users, like derives. For example, none of the standard derive macros (`Clone`, `Hash`, `Ord`, `PartialEq`, `PartialOrd`) generates implementations for any default methods, only `Eq` generates some of them for internal reasons. + +We may strongly consider changing the defaults here, and avoid the semver hazards and behavior different from existing features like derives. +This will also make the `reuse impl` syntax usable in more cases. + +In the future we may introduce a different flavor of glob delegation (with an attribute or first-class syntax), that includes methods with default bodies, and explicitly opts into potential breaking changes. ↩ [_Glob delegation_](#glob-delegation) From eb1bdbf767909b018bf9832089e21e0a0ae4c3ab Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Mon, 26 Oct 2026 19:50:29 +0300 Subject: [PATCH 12/16] Rationale: Why can target blocks be allowed without effective self parameters? --- text/0000-fn-delegation.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 1842059bef6..0d3acec4a79 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -887,6 +887,10 @@ The feature may potentially introduce a number of semver hazards: - Delegating from a public function to a private function could lead to unintended breaking changes in public interfaces, but this can be caught by a lint ([_more_](#should-the-visibility-of-the-delegation-item-be-restricted)). - Adding a non-delegatable method with default body to a trait may become a breaking change, [_depending on the design_](#why-are-methods-with-default-bodies-included-in-glob-delegation). - Changing between `Self` type alias and `Struct` in function signatures inside `impl Struct { ... }` may become a breaking change, [_depending on the design_](#why-are-effective-self-types-not-detected-through-type-aliases-or-type-equality). +- Starting to use anything with "identity" (items, closures, anonymous constants) in a macro is a breaking change if that macro is used in an ignored target block ([_more_](#why-can-target-blocks-be-allowed-without-effective-self-parameters)). + +The function body generated for delegation items may produce type checking errors, and reporting good errors from generated code is hard. +Ideally we'd need to pretty-print the generated code and point the diagnostic spans to that pretty-printed code. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -1724,8 +1728,29 @@ In feedback on [rust-lang/rfcs#1406](https://github.com/rust-lang/rfcs/pull/1406 #### Why can target blocks be allowed without effective self parameters? -TODO important: explain the rationale.
-TODO drawback: discuss future compatibility issues with identity in target blocks +Imagine an impl delegation or glob delegation, reusing all methods from some trait and delegating them to `self.inner`. +```rust +reuse impl Trait for Wrapper { self.inner } +``` + +It would be silly for this to break if `Trait` contains any "static methods" without `self`. +In that case you'd have to have to write delegation for such methods manually and separately from methods with `self`, and `reuse impl` wouldn't be usable at all. +```rust +impl Trait for Wrapper { + reuse Trait::{no_self1, no_self2}; // no target block + reuse Trait::* { self.inner } +} +``` + +To avoid this sillyness the target block is simply ignored when such a method without `self` is delegated as a part of a glob (or list), and no conversion happens. + +However, we need to make sure that the block is used by at least some generated functions in the glob or list. +Otherwise, any compiler checks after name resolution wouldn't be performed on it, including stability checks in particular. +This would create a semver hazard when some unstable API is removed and the target block stops compiling. + +The compiler also cannot currently remove any code having "identity" (items, closures, anonymous constants) during AST lowering, where the delegation desugaring happens. +So we also have to report an error when we attempt to ignore a target block with items/closures/etc in it. +This creates another semver hazard when some macro (e.g. `println!`) starts using closures or anonymous constants internally, and it breaks any code using it in ignored target blocks. ↩ [_Target block_](#target-block) From e94279b36ffd674f9e923dc20f0e588c94abdab8 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Tue, 27 Oct 2026 18:34:26 +0300 Subject: [PATCH 13/16] Rationale: Why are method-call adjustments applied to effective self parameters? --- text/0000-fn-delegation.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 0d3acec4a79..aa6e281bc8e 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -1786,7 +1786,22 @@ The more detailed discussion of the choice can be found in [this github comment] #### Why are method-call adjustments applied to effective self parameters? -TODO important: explain the rationale. +If we have a list or glob delegation, it is good if we can reuse the same target block even if the types of remapped self parameters are slightly different. +```rust +reuse Trait::{self_by_value, self_by_ref, self_by_mut_ref} { self.inner } +``` + +If method-call adjustments are applied, `self.inner` will automatically turn into `&self.inner` for `self_by_ref`, or into `&mut self.inner` for `self_by_mut_ref`, or stay as is for `self_by_value`. + +If the adjustments weren't applied, we'd have to write the code above like +```rust +reuse Trait::self_by_value { self.inner } +reuse Trait::self_by_ref { &self.inner } +reuse Trait::self_by_mut_ref { &mut self.inner } +``` +and glob and impl delegation would be very rarely usable in practice. + +The same logic applies to effective self parameters that are not literally `self`, like in the `BinOpLike` case. ↩ [_Body desugaring_](#body-desugaring) From 931f6955f1328f2f19a35c418cd98bf668711efe Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Tue, 27 Oct 2026 18:43:10 +0300 Subject: [PATCH 14/16] Rationale: Why is `self` the implicit target expression? Also add a clarification about `reuse` being conditionally reserved. --- text/0000-fn-delegation.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index aa6e281bc8e..7f4541d1f36 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -362,6 +362,8 @@ The grammar is generally modeled after `use` items, with two major differences: A delegation item starts with the `reuse` keyword ([?](#why-reuse)) and consists of a path prefix, which may be either simple or qualified, a suffix, and an optional block expression. Their roles are discussed in the following sections. +`reuse` is conditionally reserved (most similarly to `union`), at least on the existing editions, it is not treated as a keyword in non-item positions, and sometimes even in item positions, when it's required for backward compatibility (e.g. in `reuse::my_macro!();`). + Suffixes come in three flavors: individual delegation, list delegation ([?](#why-is-list-delegation-supported)), and glob delegation ([?](#why-is-glob-delegation-supported)). The optional `as IDENTIFIER` clause allows the delegated function to be defined with a different name ([?](#why-is-renaming-supported)). A delegation item intentionally does not provide syntax for introducing its own generics ([?](#why-doesnt-a-delegation-item-provide-syntax-for-introducing-its-own-generics)). A delegation item intentionally does not provide syntax for argument or return-value transformations, besides the target block ([?](#why-doesnt-a-delegation-item-provide-syntax-for-argument-or-return-value-transformations)). @@ -1756,7 +1758,20 @@ This creates another semver hazard when some macro (e.g. `println!`) starts usin #### Why is `self` the implicit target expression? -TODO: explain the rationale. +Typically, if a block doesn't have a trailing expression, a unit expression `()` is implicitly added as trailing. + +But for delegation target blocks mapping `self` into `()` almost never make sense. +So they instead do some other thing that may be actually useful for them - adding `self` as the trailing expression and therefore avoiding the argument conversion. +```rust +reuse print_what_is_mapped { dbg!(&self.inner); } + +// (Partially) desugars into +reuse print_what_is_mapped { dbg!(&self.inner); self } +``` + +So the target block can be used purely for its side effects instead of the argument conversion. + +The main alternative is to simply prohibit target blocks without a trailing expression. ↩ [_Target block_](#target-block) From 3e5e6dd8d54d0305ccc21ec9a4f521d920c7991b Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Wed, 28 Oct 2026 18:55:56 +0300 Subject: [PATCH 15/16] Rationale: Why are effective self types identified beyond the receiver? --- text/0000-fn-delegation.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index 7f4541d1f36..b69bfa06032 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -1535,7 +1535,20 @@ Also see [_Future possibilities: More sophisticated inference of generic paramet #### Why are effective self types identified beyond the receiver? -TODO: explain the rationale. +This is primarily done to support delegating `BinOp`-like and `Clone`-like traits. +One popular example would be delegation of arithmetic traits for newtypes wrapping integers. + +```rust +trait BinOpLike { + // Both `self` and `rhs` need to be mapped using the target block. + fn bin_op(&self, rhs: &Self); +} + +trait CloneLike { + // `self` needs to be mapped using the target block, and the return value needs to be wrapped. + fn clowne(&self) -> Self; +} +``` ↩ [_Effective self type identification_](#effective-self-type-identification) From bca9da465d8eca18c853f0ecc48f92035c92527f Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Wed, 28 Oct 2026 19:04:01 +0300 Subject: [PATCH 16/16] Rationale: Why can names from glob delegations be overridden by explicit items? --- text/0000-fn-delegation.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/text/0000-fn-delegation.md b/text/0000-fn-delegation.md index b69bfa06032..20e0a3976ba 100644 --- a/text/0000-fn-delegation.md +++ b/text/0000-fn-delegation.md @@ -1154,7 +1154,31 @@ List delegations list all their names explicitly, so they don't need any similar #### Why can names from glob delegations be overridden by explicit items? -TODO: explain the rationale. +If most methods in a trait are delegatable, but one of them is not delegatable, then it's good to provide a possibility to implement such method explicitly while delegating everything else. + +At least with the current restrictions explicit overrides for glob delegation are also required when delegating a trait with associated types or constants. +If not overridden they will produce errors on delegation attempts. + +```rust +trait Trait { + type Type; + const CONST: u8; + fn foo(&self); +} + +impl Trait for Wrapper { + reuse Trait::*; // ERROR attempting to delegate a constant or type +} +impl Trait for Wrapper { + type Type = u8; + const CONST: u8 = 10; + reuse Trait::*; // OK, only `fn foo` is delegated +} +``` + +It also doesn't seem generally right to provide a glob feature without any opportunity to override something explicity if the glob default doesn't fit. +Specific examples are welcome. +Regular `use` items support such "overriding" as well, because explicit names shadow glob imported names. ↩ [_Glob delegation_](#glob-delegation)