From 5e4af1f37ce8df86c590dd5c48092ea29cc10001 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Tue, 20 Jan 2026 20:40:43 +0400 Subject: [PATCH 01/16] obj-action-method-disambiguation rfc commit --- text/0000-obj-action-method-disambiguation.md | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 text/0000-obj-action-method-disambiguation.md diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md new file mode 100644 index 00000000000..dc4e029acf1 --- /dev/null +++ b/text/0000-obj-action-method-disambiguation.md @@ -0,0 +1,148 @@ +- Feature Name: `obj-action-method-disambiguation` +- Start Date: 2026-01-20 +- 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 +[summary]: #summary + +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: + +1. **Ad-hoc Disambiguation**: `expr.(Trait::method)(args)` allows invoking a specific trait method inline without breaking the method chain. +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. + +## Motivation +[motivation]: #motivation + +Currently, Rust's "Fully Qualified Syntax" (UFCS), e.g., `Trait::method(&obj)`, is the only mechanism to resolve method name conflicts between inherent implementations and traits, or between multiple traits. + +While robust, UFCS forces a reversal of the visual data flow, breaking the fluent interface pattern: +* **Fluent (Ideal)**: `object.process().output()` +* **Broken (Current)**: `Trait::output(&object.process())` + +This creates significant friction: +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. +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. + +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). + +## Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +### The Problem: Ambiguous Methods + +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`. + +```rust +struct Image; +impl Image { + fn rotate(&self) { println!("Optimized internal rotation"); } +} + +use graphic_lib::Transform; +impl Transform for Image { + fn rotate(&self) { println!("Generic geometric rotation"); } + fn crop(&self) { ... } +} +``` + +Calling `img.rotate()` is now ambiguous or defaults to the inherent method when you might intend to use the trait implementation. + +### Solution 1: Ad-hoc Disambiguation (The "Quick Fix") + +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: + +```rust +// Calls the Trait implementation while maintaining the chain +img.crop().(Transform::rotate)().save(); +``` + +This tells the compiler: "Use the `rotate` method from the `Transform` trait on this object." + +**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. + +### Solution 2: Definition-site Aliases (The "API Design" Fix) + +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. + +```rust +impl Image { + // Inherent method + fn rotate(&self) { ... } + + // Expose the Transform trait as 'OtherOps' (Operations) + pub use graphic_lib::Transform as OtherOps; +} +``` + +Now, users can access these methods via the alias, which is conceptually part of the `Image` type: + +```rust +let img = Image::new(); + +img.Self::rotate(); // Explicitly calls inherent (Optimized) +img.OtherOps::rotate(); // Calls via Alias -> Transform (Generic) +``` + +The `Self` keyword is implicitly treated as an alias for the inherent implementation, ensuring symmetry. + +## Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +### Grammar Extensions + +The `MethodCallExpr` grammar is extended in two specific ways: + +1. **Parenthesized Path**: `Expr '.' '(' TypePath ')' '(' Args ')'` + * This syntax is used for **Ad-hoc Disambiguation**. + * **Resolution**: The `TypePath` is resolved. If it resolves to a trait method, it is invoked with `Expr` as the receiver (the first argument). + * **Desugaring**: `obj.(Path::method)(args)` desugars to `Path::method(obj, args)`, ensuring correct autoref/autoderef behavior for `obj`. + * **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()`. + +2. **Aliased Path**: `Expr '.' Ident '::' Ident '(' Args ')'` + * This syntax is used for **Definition-site Aliases**. + * **Resolution**: The first `Ident` is looked up in the `impl` block of the `Expr`'s type. + * **Alias Matching**: + * If `Ident` matches a `pub use Trait as Alias;` statement, the call resolves to `::method`. + * The keyword `Self` is implicitly treated as an alias for the inherent implementation. `obj.Self::method()` resolves to the inherent method. + +### Resolution Logic Summary + +* **Case: `obj.(Trait::method)(...)`** + * Compiler verifies `Trait` is in scope or fully qualified. + * Resolves to UFCS call with `obj` as first arg. + +* **Case: `obj.Alias::method(...)`** + * Compiler looks up `Alias` in `obj`'s type definition. + * If found, maps to corresponding Trait implementation. + +## Drawbacks +[drawbacks]: #drawbacks + +* **Parser Complexity**: The parser requires lookahead or distinct rules to distinguish `.` followed by `(` (method call) versus `.` followed by `Ident` followed by `::` (aliased call). +* **Punctuation Noise**: The syntax `.(...)` introduces more "Perl-like" punctuation symbols to the language, which some may find unaesthetic. + +## Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +* **Why Aliases?** + * 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. + * It mirrors C++ explicit qualification (e.g., `obj.Base::method()`). +* **Why Parentheses for Ad-hoc?** + * `obj.Trait::method` is syntactically ambiguous with field access. + * `obj.(Trait::method)` is unambiguous and visually distinct. + +## Prior art +[prior-art]: #prior-art + +* **C++**: Allows explicit qualification of method calls using `obj.Base::method()`, which served as inspiration for the Aliased Path syntax. + +## Unresolved questions +[unresolved-questions]: #unresolved-questions + +* **Syntax Choice**: Should we consider other bracket types to avoid confusion with tuple grouping? (e.g., `obj.{Trait::method}()` or `obj.[Trait::method]()`). + +## Future possibilities +[future-possibilities]: #future-possibilities + +* **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. From 416ca383767d9174629e28210c168f3a8135f959 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Tue, 20 Jan 2026 21:00:44 +0400 Subject: [PATCH 02/16] Tiny adjustment 1 --- text/0000-obj-action-method-disambiguation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index dc4e029acf1..072f0429857 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -14,7 +14,7 @@ This RFC proposes two extensions to Rust's method call syntax to unify method re ## Motivation [motivation]: #motivation -Currently, Rust's "Fully Qualified Syntax" (UFCS), e.g., `Trait::method(&obj)`, is the only mechanism to resolve method name conflicts between inherent implementations and traits, or between multiple traits. +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. While robust, UFCS forces a reversal of the visual data flow, breaking the fluent interface pattern: * **Fluent (Ideal)**: `object.process().output()` From 983363dc924d1b2bf17a164856c4d3d2cb81bb00 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Tue, 20 Jan 2026 21:43:02 +0400 Subject: [PATCH 03/16] added new inherent impl items --- text/0000-obj-action-method-disambiguation.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index 072f0429857..bb0486882b2 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -106,6 +106,11 @@ The `MethodCallExpr` grammar is extended in two specific ways: * If `Ident` matches a `pub use Trait as Alias;` statement, the call resolves to `::method`. * The keyword `Self` is implicitly treated as an alias for the inherent implementation. `obj.Self::method()` resolves to the inherent method. +3. **Inherent Impl Items**: + * A `use Trait as Alias;` item is now valid within an inherent `impl` block. + * `use Trait;` is also supported as a shorthand for `use Trait as Trait;`. + * Visibility modifiers (e.g., `pub`) are supported and determine the visibility of the alias for method resolution. + ### Resolution Logic Summary * **Case: `obj.(Trait::method)(...)`** From e50c1ab5146c71da8a9d03ecf5e5256d339209eb Mon Sep 17 00:00:00 2001 From: Syudoer Date: Tue, 20 Jan 2026 21:53:13 +0400 Subject: [PATCH 04/16] Added the main drawback --- text/0000-obj-action-method-disambiguation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index bb0486882b2..025dc18dde3 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -124,6 +124,7 @@ The `MethodCallExpr` grammar is extended in two specific ways: ## Drawbacks [drawbacks]: #drawbacks +* **Cognitive Load** Increasing the cognitive load of users and Rust developers by adding more features * **Parser Complexity**: The parser requires lookahead or distinct rules to distinguish `.` followed by `(` (method call) versus `.` followed by `Ident` followed by `::` (aliased call). * **Punctuation Noise**: The syntax `.(...)` introduces more "Perl-like" punctuation symbols to the language, which some may find unaesthetic. From a7f630bbc11818ce1019269da8b9a584291e4cde Mon Sep 17 00:00:00 2001 From: Syudoer Date: Wed, 21 Jan 2026 11:00:04 +0400 Subject: [PATCH 05/16] Extended the Cognitive Load drawback --- text/0000-obj-action-method-disambiguation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index 025dc18dde3..db83ebc7277 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -124,7 +124,7 @@ The `MethodCallExpr` grammar is extended in two specific ways: ## Drawbacks [drawbacks]: #drawbacks -* **Cognitive Load** Increasing the cognitive load of users and Rust developers by adding more features +* **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 * **Parser Complexity**: The parser requires lookahead or distinct rules to distinguish `.` followed by `(` (method call) versus `.` followed by `Ident` followed by `::` (aliased call). * **Punctuation Noise**: The syntax `.(...)` introduces more "Perl-like" punctuation symbols to the language, which some may find unaesthetic. From b3ff0885317235149599172b280efa84c2cabb13 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Wed, 21 Jan 2026 11:14:50 +0400 Subject: [PATCH 06/16] Changed reason for parentheses for ad-hoc usage --- text/0000-obj-action-method-disambiguation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index db83ebc7277..1319b776f5e 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -135,8 +135,8 @@ The `MethodCallExpr` grammar is extended in two specific ways: * 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. * It mirrors C++ explicit qualification (e.g., `obj.Base::method()`). * **Why Parentheses for Ad-hoc?** - * `obj.Trait::method` is syntactically ambiguous with field access. - * `obj.(Trait::method)` is unambiguous and visually distinct. + * `obj.Trait::method` looks like there is something called `Trait` inside the `obj` while `Trait` is coming from the scope of the call + * `obj.(Trait::method)` shows that `Trait::method` is evaluated first and then applied to the obj ## Prior art [prior-art]: #prior-art From c56616d40ba4ec2fba052d55828c4d2ff042ec5d Mon Sep 17 00:00:00 2001 From: Syudoer Date: Thu, 22 Jan 2026 22:46:25 +0400 Subject: [PATCH 07/16] Major Rewrite --- text/0000-obj-action-method-disambiguation.md | 193 ++++++++++++------ 1 file changed, 131 insertions(+), 62 deletions(-) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index 1319b776f5e..ca0bacbcf75 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -8,83 +8,159 @@ 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: -1. **Ad-hoc Disambiguation**: `expr.(Trait::method)(args)` allows invoking a specific trait method inline without breaking the method chain. -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. +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) +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) ## Motivation [motivation]: #motivation -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. +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. While robust, UFCS forces a reversal of the visual data flow, breaking the fluent interface pattern: * **Fluent (Ideal)**: `object.process().output()` * **Broken (Current)**: `Trait::output(&object.process())` -This creates significant friction: -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. -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. - -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). - ## Guide-level explanation [guide-level-explanation]: #guide-level-explanation -### The Problem: Ambiguous Methods +There are three ways to have something callable inside of `obj`: +- as a field containing a pointer to a function +- as an inherent method +- as a trait method -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`. +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. +Imagine you have this piece of code ```rust -struct Image; -impl Image { - fn rotate(&self) { println!("Optimized internal rotation"); } +use std::fmt::Display; + +struct SomeThing { + something: fn(T), +} + +impl SomeThing { + fn something(&self, _arg: T) { + println!("inherent fn called, got {}", _arg) + } +} + +trait SomeTrait { + fn something(&self, _arg: T); } -use graphic_lib::Transform; -impl Transform for Image { - fn rotate(&self) { println!("Generic geometric rotation"); } - fn crop(&self) { ... } +impl SomeTrait for SomeThing { + fn something(&self, _arg: T) { + println!("trait fn called, got {}", _arg); + + print!("\t"); + self.something(_arg); + } +} + +fn main() { + let value = SomeThing { something: |_arg: i32| {println!("fn pointer called, got {_arg}")} }; + + value.something(1); + (value.something)(2); + SomeTrait::something(&value, 3); } ``` -Calling `img.rotate()` is now ambiguous or defaults to the inherent method when you might intend to use the trait implementation. +it works, it handles all three ways and prints +```plain +inherent fn called, got 1 +fn pointer called, got 2 +trait fn called, got 3 + inherent fn called, got 3 +``` -### Solution 1: Ad-hoc Disambiguation (The "Quick Fix") +but if you change the line `impl SomeTrait for SomeThing {` to `impl SomeTrait for SomeThing {` instead of producing an error for the mismatch, the code compiles successfully and prints + +```plain +inherent fn called, got 1 +fn pointer called, got 2 +trait fn called, got 3 + trait fn called, got 3 + trait fn called, got 3 + trait fn called, got 3 + trait fn called, got 3 + trait fn called, got 3 + trait fn called, got 3 + trait fn called, got 3 + trait fn called, got 3 + trait fn called, got 3 + trait fn called, got 3 + ... +``` -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: +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 +```rust +impl SomeTrait for SomeThing { + fn something(&self, _arg: T) { + println!("trait fn called, got {}", _arg); + + print!("\t"); + self.something(_arg); // here + } +} +``` + +To prevent this and ensure the compiler rejects broken code, it would be better to use `self.Self::something(_arg)` instead of `self.something(_arg)`. ```rust -// Calls the Trait implementation while maintaining the chain -img.crop().(Transform::rotate)().save(); +impl SomeTrait for SomeThing { + fn something(&self, _arg: T) { + println!("trait fn called, got {}", _arg); + + print!("\t"); + self.Self::something(_arg); + } +} ``` -This tells the compiler: "Use the `rotate` method from the `Transform` trait on this object." +`value.Self::method()` allows the compiler to only use an inherent method called `method` and errors if it hasn't been found. -**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. +### Method Chain Conflicts -### Solution 2: Definition-site Aliases (The "API Design" Fix) +Sometimes the ambiguity arises not within an implementation, but when using a type that implements traits with overlapping method names. -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. +Consider a scenario where you have a `Builder` struct that implements both a `Reset` trait and has an inherent `reset` method. ```rust -impl Image { - // Inherent method - fn rotate(&self) { ... } +struct Builder; +impl Builder { + fn build(&self) -> String { "done".to_string() } + fn reset(&self) -> &Self { self } +} + +trait Reset { + fn reset(&self) -> &Self; +} + +impl Reset for Builder { + fn reset(&self) -> &Self { self } +} - // Expose the Transform trait as 'OtherOps' (Operations) - pub use graphic_lib::Transform as OtherOps; +fn main() { + let b = Builder; + // Defaults to the inherent method `reset` but silently falls back to the trait implementation if the inherent method is removed or renamed + b.reset().build(); } ``` -Now, users can access these methods via the alias, which is conceptually part of the `Image` type: +Using the new syntax, you can explicitly choose which method to use without breaking the chain: ```rust -let img = Image::new(); +fn main() { + let b = Builder; -img.Self::rotate(); // Explicitly calls inherent (Optimized) -img.OtherOps::rotate(); // Calls via Alias -> Transform (Generic) -``` + // Use the inherent reset method + b.Self::reset().build(); -The `Self` keyword is implicitly treated as an alias for the inherent implementation, ensuring symmetry. + // Use the trait's reset method explicitly + b.(Reset::reset)().build(); +} +``` ## Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -94,22 +170,15 @@ The `Self` keyword is implicitly treated as an alias for the inherent implementa The `MethodCallExpr` grammar is extended in two specific ways: 1. **Parenthesized Path**: `Expr '.' '(' TypePath ')' '(' Args ')'` - * This syntax is used for **Ad-hoc Disambiguation**. + * This syntax is used for **Explicit Trait Method Calls**. * **Resolution**: The `TypePath` is resolved. If it resolves to a trait method, it is invoked with `Expr` as the receiver (the first argument). * **Desugaring**: `obj.(Path::method)(args)` desugars to `Path::method(obj, args)`, ensuring correct autoref/autoderef behavior for `obj`. * **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()`. -2. **Aliased Path**: `Expr '.' Ident '::' Ident '(' Args ')'` - * This syntax is used for **Definition-site Aliases**. - * **Resolution**: The first `Ident` is looked up in the `impl` block of the `Expr`'s type. - * **Alias Matching**: - * If `Ident` matches a `pub use Trait as Alias;` statement, the call resolves to `::method`. - * The keyword `Self` is implicitly treated as an alias for the inherent implementation. `obj.Self::method()` resolves to the inherent method. - -3. **Inherent Impl Items**: - * A `use Trait as Alias;` item is now valid within an inherent `impl` block. - * `use Trait;` is also supported as a shorthand for `use Trait as Trait;`. - * Visibility modifiers (e.g., `pub`) are supported and determine the visibility of the alias for method resolution. +2. **Explicit Inherent Path**: `Expr '.' 'Self' '::' Ident '(' Args ')'` + * This syntax is used for **Explicit Inherent Method Calls**. + * **Resolution**: The `Ident` is looked up strictly within the inherent implementation of `Expr`'s type. + * **Semantics**: `obj.Self::method()` resolves to the inherent method `method`. It effectively bypasses trait method lookup. ### Resolution Logic Summary @@ -117,31 +186,30 @@ The `MethodCallExpr` grammar is extended in two specific ways: * Compiler verifies `Trait` is in scope or fully qualified. * Resolves to UFCS call with `obj` as first arg. -* **Case: `obj.Alias::method(...)`** - * Compiler looks up `Alias` in `obj`'s type definition. - * If found, maps to corresponding Trait implementation. +* **Case: `obj.Self::method(...)`** + * Compiler looks up `method` in `obj`'s inherent implementation. ## Drawbacks [drawbacks]: #drawbacks -* **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 -* **Parser Complexity**: The parser requires lookahead or distinct rules to distinguish `.` followed by `(` (method call) versus `.` followed by `Ident` followed by `::` (aliased call). +* **Parser Complexity**: The parser requires lookahead or distinct rules to distinguish `.` followed by `(` (method call) versus `.` followed by `Self` followed by `::`. * **Punctuation Noise**: The syntax `.(...)` introduces more "Perl-like" punctuation symbols to the language, which some may find unaesthetic. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -* **Why Aliases?** - * 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. - * It mirrors C++ explicit qualification (e.g., `obj.Base::method()`). -* **Why Parentheses for Ad-hoc?** - * `obj.Trait::method` looks like there is something called `Trait` inside the `obj` while `Trait` is coming from the scope of the call - * `obj.(Trait::method)` shows that `Trait::method` is evaluated first and then applied to the obj +* **Why Parentheses for Trait Method Calls?** + * `value.Trait::method` looks like there is something called `Trait` inside the `value` while `Trait` is coming from the scope of the call. + * `value.(Trait::method)` shows that `Trait::method` is evaluated first and then applied to the `value`. + * **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. +* **Why No Parentheses for Inherent Method Call?** + * Unlike `Trait`, which comes from the outer scope, `Self` conceptually belongs to the instance itself. + * `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. ## Prior art [prior-art]: #prior-art -* **C++**: Allows explicit qualification of method calls using `obj.Base::method()`, which served as inspiration for the Aliased Path syntax. +* **C++**: Allows explicit qualification of method calls using `obj.Base::method()`. ## Unresolved questions [unresolved-questions]: #unresolved-questions @@ -152,3 +220,4 @@ The `MethodCallExpr` grammar is extended in two specific ways: [future-possibilities]: #future-possibilities * **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. +* **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. \ No newline at end of file From 2ab02f5bd3d35b9bbc46fbadf023661739dc5107 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Thu, 22 Jan 2026 23:03:02 +0400 Subject: [PATCH 08/16] Corrected some typos --- text/0000-obj-action-method-disambiguation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index ca0bacbcf75..41dce7ffa7f 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -9,7 +9,7 @@ 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: 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) -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) +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) ## Motivation [motivation]: #motivation @@ -23,12 +23,12 @@ While robust, UFCS forces a reversal of the visual data flow, breaking the fluen ## Guide-level explanation [guide-level-explanation]: #guide-level-explanation -There are three ways to have something callable inside of `obj`: +There are three ways to have something callable on `obj`: - as a field containing a pointer to a function - as an inherent method - as a trait method -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. +While the first one is not confusing and has its unique syntax `(value.field)(args)`, the other two may cause some unexpected and completely unrelated errors. Imagine you have this piece of code ```rust From 012817b5ee694df82f5316cb3c1f8eed3a04f6f1 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Thu, 22 Jan 2026 23:12:25 +0400 Subject: [PATCH 09/16] Corrected some typos 2 --- text/0000-obj-action-method-disambiguation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index 41dce7ffa7f..5ee3ac9061f 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -58,7 +58,7 @@ impl SomeTrait for SomeThing { } fn main() { - let value = SomeThing { something: |_arg: i32| {println!("fn pointer called, got {_arg}")} }; + let value = SomeThing { something: |_arg: i32| {println!("fn pointer called, got {}", _arg)} }; value.something(1); (value.something)(2); From d2f698bb82a567ff20e56776d4ae36f20d0cd1dd Mon Sep 17 00:00:00 2001 From: Syudoer Date: Thu, 22 Jan 2026 23:14:35 +0400 Subject: [PATCH 10/16] Corrected some typos 3 --- text/0000-obj-action-method-disambiguation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index 5ee3ac9061f..d629dd9998b 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -142,7 +142,7 @@ impl Reset for Builder { } fn main() { - let b = Builder; + let b = Builder {}; // Defaults to the inherent method `reset` but silently falls back to the trait implementation if the inherent method is removed or renamed b.reset().build(); } From 70322f11ad96993f22b8c8625dd186e3c79d07c5 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Thu, 22 Jan 2026 23:18:40 +0400 Subject: [PATCH 11/16] Corrected some typos 4 --- text/0000-obj-action-method-disambiguation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index d629dd9998b..6fdf8f3d1ae 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -28,7 +28,7 @@ There are three ways to have something callable on `obj`: - as an inherent method - as a trait method -While the first one is not confusing and has its unique syntax `(value.field)(args)`, the other two may cause some unexpected and completely unrelated errors. +While the first one is not confusing and has its unique syntax `(value.field)(args)`, the other two may cause some unexpected errors that seem unrelated to the actual mistake. Imagine you have this piece of code ```rust From d5159b2819a04b35d1275f3fbc83057aac7e511c Mon Sep 17 00:00:00 2001 From: Syudoer Date: Thu, 22 Jan 2026 23:49:58 +0400 Subject: [PATCH 12/16] changed the PR number --- text/0000-obj-action-method-disambiguation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/0000-obj-action-method-disambiguation.md index 6fdf8f3d1ae..4557966b494 100644 --- a/text/0000-obj-action-method-disambiguation.md +++ b/text/0000-obj-action-method-disambiguation.md @@ -1,6 +1,6 @@ - Feature Name: `obj-action-method-disambiguation` - Start Date: 2026-01-20 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3908](https://github.com/rust-lang/rfcs/pull/3908) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) ## Summary From 37861889bedb01d449a99e402083c099c8bb32f8 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Thu, 22 Jan 2026 23:54:16 +0400 Subject: [PATCH 13/16] Renamed the md file --- ...disambiguation.md => 3908-obj-action-method-disambiguation.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-obj-action-method-disambiguation.md => 3908-obj-action-method-disambiguation.md} (100%) diff --git a/text/0000-obj-action-method-disambiguation.md b/text/3908-obj-action-method-disambiguation.md similarity index 100% rename from text/0000-obj-action-method-disambiguation.md rename to text/3908-obj-action-method-disambiguation.md From 86bd195e5c85ed244586f22c7120c48102164030 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Fri, 23 Jan 2026 17:45:00 +0400 Subject: [PATCH 14/16] Extended the Motivation section and added some other details --- text/3908-obj-action-method-disambiguation.md | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/text/3908-obj-action-method-disambiguation.md b/text/3908-obj-action-method-disambiguation.md index 4557966b494..5a50baf8ff5 100644 --- a/text/3908-obj-action-method-disambiguation.md +++ b/text/3908-obj-action-method-disambiguation.md @@ -6,7 +6,7 @@ ## Summary [summary]: #summary -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: +This RFC proposes two extensions to Rust's method call syntax to unify method call syntax and maintain fluent method chaining ("noun-verb" style) in the presence of naming ambiguities: 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) 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) @@ -14,16 +14,29 @@ This RFC proposes two extensions to Rust's method call syntax to unify method re ## Motivation [motivation]: #motivation +### Method chain break 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. While robust, UFCS forces a reversal of the visual data flow, breaking the fluent interface pattern: * **Fluent (Ideal)**: `object.process().output()` * **Broken (Current)**: `Trait::output(&object.process())` +### Silent bugs and Fragility + +Currently, Rust's method resolution follows a fixed priority: it defaults to an inherent method if one exists. If no inherent method is found, the compiler looks for traits in scope that provide the method. If exactly one such trait is implemented for the type, the compiler selects it; otherwise, it returns an error. + +This creates a "Primary and Fallback" mechanism where the compiler can silently switch between logic. If a primary (inherent) method is removed or renamed, the compiler may silently fall back to a trait implementation. Conversely, adding an inherent method can unexpectedly shadow an existing trait method call. + +In rare cases, modifying one part of the code can unexpectedly alter logic elsewhere, causing a chain reaction of errors that makes it difficult to locate the root cause. + +### Summary + +This RFC aims to fully solve the problem of fluent method chaining. The second problem (fragility) requires a more complex approach, with this RFC being the first step. More details on potential future solutions are discussed in the [Future Possibilities](#future-possibilities) section. + ## Guide-level explanation [guide-level-explanation]: #guide-level-explanation -There are three ways to have something callable on `obj`: +There are three ways to have something callable inside of `obj`: - as a field containing a pointer to a function - as an inherent method - as a trait method @@ -93,6 +106,8 @@ trait fn called, got 3 ... ``` +NOTE: If instead of `impl SomeTrait for SomeThing {` you wrote `impl SomeTrait for SomeThing {` the compiler would return a comprehensible error but because you've forgotten `+ Display` the compiler does not even try to do what you suppose it to do + 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 ```rust impl SomeTrait for SomeThing { @@ -173,7 +188,7 @@ The `MethodCallExpr` grammar is extended in two specific ways: * This syntax is used for **Explicit Trait Method Calls**. * **Resolution**: The `TypePath` is resolved. If it resolves to a trait method, it is invoked with `Expr` as the receiver (the first argument). * **Desugaring**: `obj.(Path::method)(args)` desugars to `Path::method(obj, args)`, ensuring correct autoref/autoderef behavior for `obj`. - * **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()`. + * **Restriction**: `Expr.(Self::Ident)(Args)` is not allowed. 2. **Explicit Inherent Path**: `Expr '.' 'Self' '::' Ident '(' Args ')'` * This syntax is used for **Explicit Inherent Method Calls**. @@ -201,7 +216,8 @@ The `MethodCallExpr` grammar is extended in two specific ways: * **Why Parentheses for Trait Method Calls?** * `value.Trait::method` looks like there is something called `Trait` inside the `value` while `Trait` is coming from the scope of the call. * `value.(Trait::method)` shows that `Trait::method` is evaluated first and then applied to the `value`. - * **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. + * **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 around trait paths avoids closing the door on this design space. + * **Why No Parentheses for Inherent Method Call?** * Unlike `Trait`, which comes from the outer scope, `Self` conceptually belongs to the instance itself. * `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. @@ -214,7 +230,7 @@ The `MethodCallExpr` grammar is extended in two specific ways: ## Unresolved questions [unresolved-questions]: #unresolved-questions -* **Syntax Choice**: Should we consider other bracket types to avoid confusion with tuple grouping? (e.g., `obj.{Trait::method}()` or `obj.[Trait::method]()`). +* **Syntax Choice**: Should we consider other bracket types to avoid confusion with tuple grouping? (e.g., `obj.{Trait::method}()` or `obj.[Trait::method]()`)? ## Future possibilities [future-possibilities]: #future-possibilities From 5c13f85158066b4708acace5f26d165ffe9122e1 Mon Sep 17 00:00:00 2001 From: Syudoer Date: Fri, 23 Jan 2026 21:17:06 +0400 Subject: [PATCH 15/16] Added `<>` as an alternative type of parentheses --- text/3908-obj-action-method-disambiguation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3908-obj-action-method-disambiguation.md b/text/3908-obj-action-method-disambiguation.md index 5a50baf8ff5..280c31ce58e 100644 --- a/text/3908-obj-action-method-disambiguation.md +++ b/text/3908-obj-action-method-disambiguation.md @@ -230,7 +230,7 @@ The `MethodCallExpr` grammar is extended in two specific ways: ## Unresolved questions [unresolved-questions]: #unresolved-questions -* **Syntax Choice**: Should we consider other bracket types to avoid confusion with tuple grouping? (e.g., `obj.{Trait::method}()` or `obj.[Trait::method]()`)? +* **Syntax Choice**: Should we consider other bracket types to avoid confusion with tuple grouping? (e.g., `obj.()` or `obj.[Trait::method]()` or)? ## Future possibilities [future-possibilities]: #future-possibilities From 08670256b04bc8eebaf1c2b9b1aab8703b4fa69e Mon Sep 17 00:00:00 2001 From: Syudoer Date: Fri, 23 Jan 2026 21:28:40 +0400 Subject: [PATCH 16/16] Corrected a typo --- text/3908-obj-action-method-disambiguation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3908-obj-action-method-disambiguation.md b/text/3908-obj-action-method-disambiguation.md index 280c31ce58e..4e1bfe82fa9 100644 --- a/text/3908-obj-action-method-disambiguation.md +++ b/text/3908-obj-action-method-disambiguation.md @@ -230,7 +230,7 @@ The `MethodCallExpr` grammar is extended in two specific ways: ## Unresolved questions [unresolved-questions]: #unresolved-questions -* **Syntax Choice**: Should we consider other bracket types to avoid confusion with tuple grouping? (e.g., `obj.()` or `obj.[Trait::method]()` or)? +* **Syntax Choice**: Should we consider other bracket types to avoid confusion with tuple grouping? (e.g., `obj.()` or `obj.[Trait::method]()`)? ## Future possibilities [future-possibilities]: #future-possibilities