You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This RFC proposes two extensions to Rust's method call syntax to unify method resolution and maintain fluent method chaining ("noun-verb" style) in the presence of naming ambiguities:
10
10
11
-
1.**Ad-hoc Disambiguation**: `expr.(Trait::method)(args)` allows invoking a specific trait method inline without breaking the method chain.
12
-
2.**Definition-site Aliases**: `pub use Trait as Alias;` within `impl` blocks enables `expr.Alias::method(args)`, allowing type authors to expose traits as named "facets" of their API.
11
+
1.**Trait Method Call**: `expr.(path::to::Trait::method)(args)` allows invoking a specific trait's method inline without breaking the method chain. (The parentheses around `path::to::Trait::method` are required)
12
+
2.**inherent Method Call**: `expr.Self::method(args)` is an explicit way to call an inherent method. (Unlike the previous case parentheses are not allowed)
13
13
14
14
## Motivation
15
15
[motivation]: #motivation
16
16
17
-
Currently, Rust's "Fully Qualified Syntax" (UFCS), e.g., `Trait::method(&obj)`, is the main mechanism to resolve method name conflicts between inherent implementations and traits, or between multiple traits.
17
+
Currently, Rust's "Fully Qualified Syntax" (UFCS), e.g., `Trait::method(&obj)`, is the main mechanism to disambiguate method calls between inherent implementations and traits, or between multiple traits.
18
18
19
19
While robust, UFCS forces a reversal of the visual data flow, breaking the fluent interface pattern:
1.**Cognitive Load**: The user must stop writing logic, look up the full trait path, import it, and restructure the code to wrap the object in a function call.
25
-
2.**API Opacity**: Consumers often do not know which specific module a trait comes from, nor should they need to manage those imports just to call a method.
26
-
27
-
We propose a solution that restores the `object.action()` left-to-right flow in all cases and provides tools for both API consumers (ad-hoc fixes) and API producers (aliases).
There are three ways to have something callable inside of `obj`:
27
+
- as a field containing a pointer to a function
28
+
- as an inherent method
29
+
- as a trait method
33
30
34
-
Imagine you are using a type `Image` that has an optimized inherent `rotate` method. Later, you import a graphics library with a `Transform` trait that also defines `rotate`.
31
+
While the first one is not confusing and has its unique syntax `(value.field)(args)`, the other two may cause some unexpected completely unrelated errors.
Calling `img.rotate()` is now ambiguous or defaults to the inherent method when you might intend to use the trait implementation.
69
+
it works, it handles all three ways and prints
70
+
```plain
71
+
inherent fn called, got 1
72
+
fn pointer called, got 2
73
+
trait fn called, got 3
74
+
inherent fn called, got 3
75
+
```
50
76
51
-
### Solution 1: Ad-hoc Disambiguation (The "Quick Fix")
77
+
but if you change the line `impl<T: Copy + Display> SomeTrait<T> for SomeThing<T> {` to `impl<T: Copy + Display, U: Copy> SomeTrait<T> for SomeThing<U> {` instead of producing an error for the mismatch, the code compiles successfully and prints
78
+
79
+
```plain
80
+
inherent fn called, got 1
81
+
fn pointer called, got 2
82
+
trait fn called, got 3
83
+
trait fn called, got 3
84
+
trait fn called, got 3
85
+
trait fn called, got 3
86
+
trait fn called, got 3
87
+
trait fn called, got 3
88
+
trait fn called, got 3
89
+
trait fn called, got 3
90
+
trait fn called, got 3
91
+
trait fn called, got 3
92
+
trait fn called, got 3
93
+
...
94
+
```
52
95
53
-
If you are a consumer of this API and need to resolve this ambiguity immediately without breaking your method chain, you can use parentheses to specify the trait:
96
+
You would also get the same undesirable behavior in another case. You could rename `something` in `SomeThing`'s impl block and forget to rename it in the `SomeTrait`'s impl block
This tells the compiler: "Use the `rotate` method from the `Transform` trait on this object."
121
+
`value.Self::method()` allows the compiler to only use an inherent method called `method` and errors if it hasn't been found.
61
122
62
-
**Note on `Self`**: While `img.(Self::rotate)()` is grammatically possible in some cases, it is discouraged. The compiler will warn you to remove the parentheses and use the explicit alias syntax described below.
123
+
### Method Chain Conflicts
63
124
64
-
### Solution 2: Definition-site Aliases (The "API Design" Fix)
125
+
Sometimes the ambiguity arises not within an implementation, but when using a type that implements traits with overlapping method names.
65
126
66
-
As the author of `Image`, you can prevent this friction for your users. Instead of forcing them to import `graphic_lib::Transform` to access specific functionality, you can expose that trait as a named part of your `Image` API.
127
+
Consider a scenario where you have a `Builder` struct that implements both a `Reset`trait and has an inherent `reset` method.
67
128
68
129
```rust
69
-
implImage {
70
-
// Inherent method
71
-
fnrotate(&self) { ... }
130
+
structBuilder;
131
+
implBuilder {
132
+
fnbuild(&self) ->String { "done".to_string() }
133
+
fnreset(&self) ->&Self { self }
134
+
}
135
+
136
+
traitReset {
137
+
fnreset(&self) ->&Self;
138
+
}
139
+
140
+
implResetforBuilder {
141
+
fnreset(&self) ->&Self { self }
142
+
}
72
143
73
-
// Expose the Transform trait as 'OtherOps' (Operations)
74
-
pubusegraphic_lib::TransformasOtherOps;
144
+
fnmain() {
145
+
letb=Builder;
146
+
// Defaults to the inherent method `reset` but silently falls back to the trait implementation if the inherent method is removed or renamed
147
+
b.reset().build();
75
148
}
76
149
```
77
150
78
-
Now, users can access these methods via the alias, which is conceptually part of the `Image` type:
151
+
Using the new syntax, you can explicitly choose which method to use without breaking the chain:
* This syntax is used for **Ad-hoc Disambiguation**.
173
+
* This syntax is used for **Explicit Trait Method Calls**.
98
174
***Resolution**: The `TypePath` is resolved. If it resolves to a trait method, it is invoked with `Expr` as the receiver (the first argument).
99
175
***Desugaring**: `obj.(Path::method)(args)` desugars to `Path::method(obj, args)`, ensuring correct autoref/autoderef behavior for `obj`.
100
176
***Restriction**: Using `(Self::method)` inside an `impl` block where `Self` is a type alias triggers a compiler warning, suggesting the removal of parentheses and usage of `Expr.Self::method()`.
* This syntax is used for **Explicit Inherent Method Calls**.
180
+
***Resolution**: The `Ident` is looked up strictly within the inherent implementation of `Expr`'s type.
181
+
***Semantics**: `obj.Self::method()` resolves to the inherent method `method`. It effectively bypasses trait method lookup.
113
182
114
183
### Resolution Logic Summary
115
184
116
185
***Case: `obj.(Trait::method)(...)`**
117
186
* Compiler verifies `Trait` is in scope or fully qualified.
118
187
* Resolves to UFCS call with `obj` as first arg.
119
188
120
-
***Case: `obj.Alias::method(...)`**
121
-
* Compiler looks up `Alias` in `obj`'s type definition.
122
-
* If found, maps to corresponding Trait implementation.
189
+
***Case: `obj.Self::method(...)`**
190
+
* Compiler looks up `method` in `obj`'s inherent implementation.
123
191
124
192
## Drawbacks
125
193
[drawbacks]: #drawbacks
126
194
127
-
***Cognitive Load** Increasing the cognitive load of users and Rust developers by adding more features. The definition-site aliases may eventually become a common way to group methods forcing users to deal with this feature not only in the case of method name disambiguation
128
-
***Parser Complexity**: The parser requires lookahead or distinct rules to distinguish `.` followed by `(` (method call) versus `.` followed by `Ident` followed by `::` (aliased call).
195
+
***Parser Complexity**: The parser requires lookahead or distinct rules to distinguish `.` followed by `(` (method call) versus `.` followed by `Self` followed by `::`.
129
196
***Punctuation Noise**: The syntax `.(...)` introduces more "Perl-like" punctuation symbols to the language, which some may find unaesthetic.
* The primary benefit is for the **consumer**. They should not need to know the origin module of a trait to use it. Aliasing bundles the dependency with the type, treating the trait as a named interface/facet of the object.
136
-
* It mirrors C++ explicit qualification (e.g., `obj.Base::method()`).
137
-
***Why Parentheses for Ad-hoc?**
138
-
*`obj.Trait::method` looks like there is something called `Trait` inside the `obj` while `Trait` is coming from the scope of the call
139
-
*`obj.(Trait::method)` shows that `Trait::method` is evaluated first and then applied to the obj
201
+
***Why Parentheses for Trait Method Calls?**
202
+
*`value.Trait::method` looks like there is something called `Trait` inside the `value` while `Trait` is coming from the scope of the call.
203
+
*`value.(Trait::method)` shows that `Trait::method` is evaluated first and then applied to the `value`.
204
+
***Reservation**: We specifically reserve the unparenthesized syntax `value.Category::method()` (where `Category` is not `Self`) for possible future language features, such as "Categorical" or "Facet" views of an object. Using parentheses for ad-hoc trait paths avoids closing the door on this design space.
205
+
***Why No Parentheses for Inherent Method Call?**
206
+
* Unlike `Trait`, which comes from the outer scope, `Self` conceptually belongs to the instance itself.
207
+
*`value.Self::method()` aligns with the mental model that `Self` is intrinsic to `value`, acting as a specific "facet" of the object itself, rather than an external function applied to it. This justifies the lack of parentheses, matching the reserved `value.Category::method()` syntax.
140
208
141
209
## Prior art
142
210
[prior-art]: #prior-art
143
211
144
-
***C++**: Allows explicit qualification of method calls using `obj.Base::method()`, which served as inspiration for the Aliased Path syntax.
212
+
***C++**: Allows explicit qualification of method calls using `obj.Base::method()`.
145
213
146
214
## Unresolved questions
147
215
[unresolved-questions]: #unresolved-questions
@@ -152,3 +220,4 @@ The `MethodCallExpr` grammar is extended in two specific ways:
152
220
[future-possibilities]: #future-possibilities
153
221
154
222
***Scoped Prioritization**: We can also introduce syntax like `use Trait for Foo` or `use Self for Foo` within a function scope to change default resolution without changing call sites.
223
+
***Disabling Inherent Preference**: A specialized macro or attribute could be introduced to opt-out of the default "inherent-first" resolution rule (effectively canceling the implicit `use Self for *`). This aligns with Rust's philosophy of explicit over implicit behavior where ambiguity exists, ensuring that code correctness is verifiable.
0 commit comments