Skip to content

Conversation

@syudoer
Copy link

@syudoer syudoer commented Jan 20, 2026

I'll say upfront that I'm a Rust beginner, but the issue I'm discussing is exactly the kind of thing that experienced developers tend to avoid altogether. I suspect the reason it still exists in the language isn't that people don't know how to solve it, but simply that it doesn't occur frequently enough to annoy someone sufficiently to push for a language-level change. For me, as someone who's just learning the language, this problem has been nagging at me, and I feel I've spent enough time thinking it through and discussing it on the forum to arrive at a logical and straightforward solution.

My RFC proposes what I believe is the most intuitive and readable syntax for disambiguating method names that I can imagine. I think even without reading the full text, it's immediately clear what obj.Self::method(args), obj.Category::method(args), and obj.(Library::Trait::method)(args) do. You can even guess why the parentheses are required around Library::Trait::method but not around obj.Self::method.

I used an LLM to help turn the idea into a proper RFC, and now I have the persistent feeling that the list of edits I want to make isn't getting any shorter. It's hard to say I'd have done a better job myself, given my English proficiency and the fact that when you've been nursing an idea for long enough, no retelling — not even your own — ever feels complete. So it's possible I'm just overthinking it and can no longer distinguish real logical or presentation issues from a mere sense of incompleteness.

I'm really looking forward to your feedback so I can focus my revisions on what actually matters — things that are unclear to someone who isn't the author of RFC, or issues I've simply overlooked.

Rendered

@syudoer syudoer changed the title obj-action-method-disambiguation rfc commit RFC: obj-action style method disambiguation Jan 20, 2026
img.OtherOps::rotate(); // Calls via Alias -> Transform (Generic)
```

The `Self` keyword is implicitly treated as an alias for the inherent implementation, ensuring symmetry.
Copy link
Member

@programmerjake programmerjake Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having some explicit syntax that calls inherent methods or errors and never tries to call trait methods is the best part of this RFC, I've often wanted that for code like:

impl MyType {
    pub const fn foo(&self) -> Bar { ... }
}

impl SomeTrait for MyType {
    fn foo(&self) -> Bar {
        // old syntax `self.foo()` is problematic since it turns into
        // infinite recursion if the inherent foo method is renamed/removed.
        // it also can be confusing to read since you have to know/guess
        // there's an inherent method `foo`

        // unambiguously call inherent method, will error if the
        // inherent foo method is renamed/removed rather than cause infinite recursion
        self.Self::foo()
    }
}

Copy link
Author

@syudoer syudoer Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think there is a need for Native trait alias? The reason is that some methods may not be strictly inherent but could be treated as if they were inherent when used outside the crate’s source code. By default, it would allow calling only methods defined in the crate where the type originates, but this could also be overridden in the implementation to be a trait that incorporates all the traits essential to the type.

Probably there is a better name for the alias

Copy link
Author

@syudoer syudoer Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think there is a need for Native trait alias? The reason is that some methods may not be strictly inherent but could be treated as if they were inherent when used outside the crate’s source code. By default, it would allow calling only methods defined in the crate where the type originates, but this could also be overridden in the implementation to be a trait that incorporates all the traits essential to the type.

Probably there is a better name for the alias

It would be useful when you implement your own Trait for a Type from some library, and Type has a method called the same as the one you implemented. You try value.Self:method(args): expecting old behaviour and the compiler says that Type doesn't have an inherent method called method while it still being a part of the library and old behaviour

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently in order for you to call a trait method the trait has to be in scope either via use or by bounds you wrote on some generic. so there currently aren't really trait methods that act like inherent methods, they're instead just traits that are in-scope.

it has been proposed to have inherent traits -- which make the trait methods behave like inherent methods -- but that isn't part of rust yet. if/when those are added, having them be accessible using a.Self::foo syntax might be nice, since by declaring the impl #[inherent] the author of the type explicitly is including all those trait methods in the inherent API of the type and presumably knows there aren't any problematic method name conflicts between those traits and the regular inherent methods.

so I don't think a.Native::foo is necessary since there isn't currently anything that can't just use a.Self::foo


## Drawbacks
[drawbacks]: #drawbacks

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The biggest drawback that is always present is missing: Increasing the cognitive load of users and Rust developers by adding more features.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's helpful to list that in every single RFC though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is helpful to list it in every RFC as a reminder, and also to maybe explicitly think of the extent of how much it causes it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I'm also not sure listing in quite this way is useful, I think Nora's reminder is very useful since this proposal creates significant ambiguity with itself by having two different features that do seemingly-similar things and look very similar but can have very different results when used in what looks like a similar way.

* If `Ident` matches a `pub use Trait as Alias;` statement, the call resolves to `<Type as Trait>::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**:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo a use Trait; in an impl block should also make paths like path::to::MyType::Trait valid wherever you might want to have a path to a trait, not just in method resolution.

Copy link
Author

@syudoer syudoer Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that the same? or you mean use can refer to traits for libraries that are not imported explicitly?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've finally understood. Yeah, I also think so.

Copy link
Member

@programmerjake programmerjake Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(edit: didn't see your comment until after I posted this)
I mean you can have in addition to the a.Trait::foo() syntax:

pub struct MyType;

impl MyType {
    pub use SomeTrait as Trait;
}

// now we can use MyType::Trait:
impl MyType::Trait for Foo {
    type Ty = String;
}

pub fn bar(a: impl MyType::Trait<Ty = ()>, b: &dyn MyType::Trait<Ty = u8>) -> <() as MyType::Trait>::Ty {
    todo!()
}

Copy link
Author

@syudoer syudoer Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo MyType::Trait should not be used over SomeModule::SomeTrait if we know that SomeModule::SomeTrait exists and what it is, e.g. in impl blocks.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But there is no reason to restrict this

Copy link
Member

@programmerjake programmerjake Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it could be useful for when you're writing some proc-macro that needs to access traits based off of syntax like MyType { field1: ... } so the macro can then generate code like <() as <MyType>::Field1>::field_properties()

@ehuss ehuss added the T-lang Relevant to the language team, which will review and decide on the RFC. label Jan 20, 2026
* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(val.field)(args) is already extant in the language as a way to explicitly use a field that has a function pointer, so while this is technically unambiguous, it is close to an existing syntax that it could be confused with. I am not sure I would call it "visually distinct". Visually distinct from the other call approach you want to introduce, maybe, but even then I'm not so sure.

I feel like your RFC breezes by the complexity of the current situation, when it should consider where it can incur more syntactic or semantic confusion or difficult-to-adjudicate edge cases: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=6bf8135485fb1c929848c8797ba4d360

Yes, I know that usually you don't have three things named the same way. I am just using this kind of worst-case scenario to illustrate, because the reality can trend closer to the worst-case scenarios than we would like.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've replaced the reason for the parentheses with a better one. As for visual distinction, if you don't split your code over multiple lines properly, parentheses don't look pleasant in any case of their usage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Visual distinction often is the opposite of pleasant, if the contrast is sufficiently harsh, so my critique of visual distinction is not about whether it looks nice.

Comment on lines 24 to 25
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this apply to the entire RFC as motivation? I get friction from lots of things, but if I am calling something on purpose I generally do know where it is, because in order to call it I must first have the idea that such a function may exist to call. Because I am not that imaginative, this "idea" usually comes from looking at the docs or source code, or via a suggestion via tools that can find it for me, like rustc or rust-analyzer. The LSP can even handle importing it for me. So this part of the motivation seems weak, because without an import, most people will not want to write

proc
    .(std::os::unix::process::CommandExt::pre_exec)(func).
    .(std::os::unix::process::CommandExt::exec)();

They will instead want to use std::os::unix::process::CommandExt; still.

Now, if this justification applies entirely to definition-site aliases, the question then becomes what the motivation is for ad-hoc disambiguation? "Quick fixes" alone? Is that worth adding it to the language, considering its other drawbacks, like "having similar-looking syntax for the same call that can dispatch to entirely different traits"?

It may be better to cut this RFC in half.

Copy link
Author

@syudoer syudoer Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main motivation is that currently, when you need to resolve a method name conflict, you’re forced to rewrite the call as a standalone function call, which breaks method chaining.

My RFC proposes two solutions:

  • Happy path: The author of the struct has provided an alias for the conflicting method and you can just use it
  • Unhappy path: There is no alias, so you have to take the longer route: identify the trait, bring it into scope (or fully qualify it), and use parentheses to explicitly signal that method resolution is being done by an external way, outside the object’s type implementation.

Comment on lines 134 to 139
* **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.
Copy link
Member

@workingjubilee workingjubilee Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This first says that Trait::method is ambiguous with field access, when field access is an ident, not a qpath. Then it says the parens are visually distinct. If QPath vs. Ident is not very distinct, then the visual distinction here of a couple parentheses seems equally weak, to me. Worse, because there is no sign requiring the trait to be "in-scope" through value.Trait::func, we can get the possibility of having introduced a trait with the same name as a "declaration-site alias".

In particular, I expect it will be very possible for an author of a type to introduce their own trait as a definition-site alias after a dependent crate has introduced their own trait with the same name. If the local style of that dependent is to prefer using definition-site aliases and ad-hoc disambiguation instead of the "direct" call syntax, then sloppy typing on something that looks similar, like forgetting the parens, dispatches to a call in an entirely different crate.

That is a fairly fine distinction to slice, and unlike having a bunch of fields with function pointers, which are probably private anyways and you must at least, when you are interacting with the type, have a concrete type and thus fully know one list of callables (or, if some fields are still generic, you can't interact with them as callables!), having two traits with similar names in publicly-reachable code is a lot more common.


## Drawbacks
[drawbacks]: #drawbacks

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I'm also not sure listing in quite this way is useful, I think Nora's reminder is very useful since this proposal creates significant ambiguity with itself by having two different features that do seemingly-similar things and look very similar but can have very different results when used in what looks like a similar way.

@syudoer
Copy link
Author

syudoer commented Jan 21, 2026

I'm gonna rewrite the RFC because I realized how overcomplicated it is and the fact that it's not one small incremental step but rather two or three.

@syudoer
Copy link
Author

syudoer commented Jan 21, 2026

I'll do it tomorrow, it's too late for me

I am gonna leave only value.Self::method() for an explicit inherent method calling and value.(Trait::method)() for disambiguation.
The main reason for the parentheses will be reserving syntax value.Category::method() for possible feature features.

I started writing this example but the compiler appeared to be too smart.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=e9d7541e8fefb293943333a3ee867683
You may play with it while I'am offline

@syudoer
Copy link
Author

syudoer commented Jan 21, 2026

I started writing this example but the compiler appeared to be too smart.

I don't know what kind of intelligence I've noticed in defaulting to the inherent method.

@workingjubilee
Copy link
Member

workingjubilee commented Jan 21, 2026

I tried to illustrate this in https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=6bf8135485fb1c929848c8797ba4d360 so you could meditate on it when improving the design (or just editing the proposal, whichever).

If a value in scope is of a type that "inherently" impls a function, and that function is called using method syntax, the compiler attempts to first select the inherent impl, even if a trait would be valid to call.

Using <Type as Trait>::function allows you to call a trait impl directly, and will fail if that trait doesn't implement that function (with many caveats about what I mean by "that trait").

Using <Type>::function resolves in a similar way to method syntax.

When dealing with a generic T (or U or whatever) we only know the type implements a trait if it has bounds on the generic informing us those traits are relevant, so we cannot call the type's inherent impl. I mean, we could first try to monomorphize it and see, but for various reasons we do not want to do that, because it can result in notoriously bad errors. A big reason people find Rust palatable despite its learning curve is because we've deliberately tried to make choices, when the tradeoffs are right, to make it easier to generate nice errors.

@burdges
Copy link

burdges commented Jan 21, 2026

We already have syntax for accessing only inherent methods:

mod no_traits {
    pub fn do_whatever(...) { ... }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=5129eabf9108e7952b0658bd9d4c98c9

@syudoer
Copy link
Author

syudoer commented Jan 22, 2026

I've rewritten the RFC. I hope it's cleaner this time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-lang Relevant to the language team, which will review and decide on the RFC.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants